Import Android SDK Platform P [4386628]

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

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: I9b8400ac92116cae4f033d173f7a5682b26ccba9
diff --git a/android/accounts/AbstractAccountAuthenticator.java b/android/accounts/AbstractAccountAuthenticator.java
index bf9bd79..a3b3a9f 100644
--- a/android/accounts/AbstractAccountAuthenticator.java
+++ b/android/accounts/AbstractAccountAuthenticator.java
@@ -175,6 +175,9 @@
                 }
                 if (result != null) {
                     response.onResult(result);
+                } else {
+                    response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "null bundle returned");
                 }
             } catch (Exception e) {
                 handleException(response, "addAccount", accountType, e);
diff --git a/android/accounts/AccountManager.java b/android/accounts/AccountManager.java
index dd6ad55..bd9c9fa 100644
--- a/android/accounts/AccountManager.java
+++ b/android/accounts/AccountManager.java
@@ -2321,6 +2321,10 @@
         private class Response extends IAccountManagerResponse.Stub {
             @Override
             public void onResult(Bundle bundle) {
+                if (bundle == null) {
+                    onError(ERROR_CODE_INVALID_RESPONSE, "null bundle returned");
+                    return;
+                }
                 Intent intent = bundle.getParcelable(KEY_INTENT);
                 if (intent != null && mActivity != null) {
                     // since the user provided an Activity we will silently start intents
diff --git a/android/animation/AnimatorSet.java b/android/animation/AnimatorSet.java
index 00d6657..1a2dc5c 100644
--- a/android/animation/AnimatorSet.java
+++ b/android/animation/AnimatorSet.java
@@ -843,7 +843,7 @@
         // Assumes forward playing from here on.
         for (int i = 0; i < mEvents.size(); i++) {
             AnimationEvent event = mEvents.get(i);
-            if (event.getTime() > currentPlayTime) {
+            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
                 break;
             }
 
@@ -1264,7 +1264,8 @@
         } else {
             for (int i = mLastEventId + 1; i < size; i++) {
                 AnimationEvent event = mEvents.get(i);
-                if (event.getTime() <= currentPlayTime) {
+                // TODO: need a function that accounts for infinite duration to compare time
+                if (event.getTime() != DURATION_INFINITE && event.getTime() <= currentPlayTime) {
                     latestId = i;
                 }
             }
diff --git a/android/annotation/NavigationRes.java b/android/annotation/NavigationRes.java
new file mode 100644
index 0000000..3af5ecf
--- /dev/null
+++ b/android/annotation/NavigationRes.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 android.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a navigation resource reference (e.g. {@code R.navigation.flow}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NavigationRes {
+}
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 4e258a3..e0ac911 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -114,6 +114,7 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.autofill.AutofillPopupWindow;
 import android.view.autofill.IAutofillWindowPresenter;
 import android.widget.AdapterView;
@@ -947,6 +948,18 @@
         return mAutofillManager;
     }
 
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        newBase.setAutofillClient(this);
+    }
+
+    /** @hide */
+    @Override
+    public final AutofillClient getAutofillClient() {
+        return this;
+    }
+
     /**
      * Called when the activity is starting.  This is where most initialization
      * should go: calling {@link #setContentView(int)} to inflate the
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index a866503..5e61727 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -16,14 +16,10 @@
 
 package android.app;
 
-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.ACTIVITY_TYPE_STANDARD;
-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_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
@@ -46,6 +42,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -675,32 +672,27 @@
 
         /** First static stack ID.
          * @hide */
-        public static final int FIRST_STATIC_STACK_ID = 0;
+        private  static final int FIRST_STATIC_STACK_ID = 0;
 
-        /** Home activity stack ID. */
-        public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
-
-        /** ID of stack where fullscreen activities are normally launched into. */
+        /** 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. */
+        /** 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. */
+        /** 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. */
+        /** ID of stack that always on top (always visible) when it exist.
+         * @hide */
         public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
 
-        /** ID of stack that contains the Recents activity. */
-        public static final int RECENTS_STACK_ID = PINNED_STACK_ID + 1;
-
-        /** ID of stack that contains activities launched by the assistant. */
-        public static final int ASSISTANT_STACK_ID = RECENTS_STACK_ID + 1;
-
         /** Last static stack stack ID.
          * @hide */
-        public static final int LAST_STATIC_STACK_ID = ASSISTANT_STACK_ID;
+        private static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
 
         /** Start of ID range used by stacks that are created dynamically.
          * @hide */
@@ -720,15 +712,6 @@
         }
 
         /**
-         * Returns true if dynamic stacks are allowed to be visible behind the input stack.
-         * @hide
-         */
-        // TODO: Figure-out a way to remove.
-        public static boolean isDynamicStacksVisibleBehindAllowed(int stackId) {
-            return stackId == PINNED_STACK_ID || stackId == ASSISTANT_STACK_ID;
-        }
-
-        /**
          * Returns true if we try to maintain focus in the current stack when the top activity
          * finishes.
          * @hide
@@ -740,15 +723,6 @@
         }
 
         /**
-         * Returns true if the input stack is affected by drag resizing.
-         * @hide
-         */
-        public static boolean isStackAffectedByDragResizing(int stackId) {
-            return isStaticStack(stackId) && stackId != PINNED_STACK_ID
-                    && stackId != ASSISTANT_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.
@@ -760,41 +734,6 @@
         }
 
         /**
-         * Return whether a stackId is a stack that be a backdrop to a translucent activity.  These
-         * are generally fullscreen stacks.
-         * @hide
-         */
-        public static boolean isBackdropToTranslucentActivity(int stackId) {
-            return stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                    || stackId == ASSISTANT_STACK_ID;
-        }
-
-        /**
-         * Returns true if animation specs should be constructed for app transition that moves
-         * the task to the specified stack.
-         * @hide
-         */
-        public static boolean useAnimationSpecForAppTransition(int stackId) {
-            // TODO: INVALID_STACK_ID is also animated because we don't persist stack id's across
-            // reboots.
-            return stackId == FREEFORM_WORKSPACE_STACK_ID
-                    || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                    || stackId == ASSISTANT_STACK_ID
-                    || stackId == DOCKED_STACK_ID
-                    || stackId == INVALID_STACK_ID;
-        }
-
-        /**
-         * Returns true if activities from stasks in the given {@param stackId} are allowed to
-         * enter picture-in-picture.
-         * @hide
-         */
-        public static boolean isAllowedToEnterPictureInPicture(int stackId) {
-            return stackId != HOME_STACK_ID && stackId != ASSISTANT_STACK_ID &&
-                    stackId != RECENTS_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
@@ -825,34 +764,18 @@
                     && stackId != DOCKED_STACK_ID;
         }
 
-        /**
-         * Returns true if the input stack id should only be present on a device that supports
-         * multi-window mode.
-         * @see android.app.ActivityManager#supportsMultiWindow
-         * @hide
-         */
-        // TODO: What about the other side of docked stack if we move this to WindowConfiguration?
-        public static boolean isMultiWindowStack(int stackId) {
-            return stackId == PINNED_STACK_ID || stackId == FREEFORM_WORKSPACE_STACK_ID
-                    || stackId == DOCKED_STACK_ID;
-        }
-
-        /**
-         * Returns true if the input {@param stackId} is HOME_STACK_ID or RECENTS_STACK_ID
-         * @hide
-         */
-        public static boolean isHomeOrRecentsStack(int stackId) {
-            return stackId == HOME_STACK_ID || stackId == RECENTS_STACK_ID;
-        }
-
-        /** Returns true if the input stack and its content can affect the device orientation.
+        /** Returns the stack id for the input windowing mode.
          * @hide */
-        public static boolean canSpecifyOrientation(int stackId) {
-            return stackId == HOME_STACK_ID
-                    || stackId == RECENTS_STACK_ID
-                    || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                    || stackId == ASSISTANT_STACK_ID
-                    || isDynamicStack(stackId);
+        // 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.
@@ -862,14 +785,9 @@
             final int windowingMode;
             switch (stackId) {
                 case FULLSCREEN_WORKSPACE_STACK_ID:
-                case HOME_STACK_ID:
-                case RECENTS_STACK_ID:
                     windowingMode = inSplitScreenMode
                             ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN;
                     break;
-                case ASSISTANT_STACK_ID:
-                    windowingMode = WINDOWING_MODE_FULLSCREEN;
-                    break;
                 case PINNED_STACK_ID:
                     windowingMode = WINDOWING_MODE_PINNED;
                     break;
@@ -884,27 +802,6 @@
             }
             return windowingMode;
         }
-
-        /** Returns the activity type 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 getActivityTypeForStackId(int stackId) {
-            final int activityType;
-            switch (stackId) {
-                case HOME_STACK_ID:
-                    activityType = ACTIVITY_TYPE_HOME;
-                    break;
-                case RECENTS_STACK_ID:
-                    activityType = ACTIVITY_TYPE_RECENTS;
-                    break;
-                case ASSISTANT_STACK_ID:
-                    activityType = ACTIVITY_TYPE_ASSISTANT;
-                    break;
-                default :
-                    activityType = ACTIVITY_TYPE_STANDARD;
-            }
-            return activityType;
-        }
     }
 
     /**
@@ -1143,6 +1040,7 @@
      * E.g. freeform, split-screen, picture-in-picture.
      * @hide
      */
+    @TestApi
     static public boolean supportsMultiWindow(Context context) {
         // On watches, multi-window is used to present essential system UI, and thus it must be
         // supported regardless of device memory characteristics.
@@ -1157,6 +1055,7 @@
      * Returns true if the system supports split screen multi-window.
      * @hide
      */
+    @TestApi
     static public boolean supportsSplitScreenMultiWindow(Context context) {
         return supportsMultiWindow(context)
                 && Resources.getSystem().getBoolean(
@@ -1636,6 +1535,12 @@
          */
         public int resizeMode;
 
+        /**
+         * The current configuration this task is in.
+         * @hide
+         */
+        final public Configuration configuration = new Configuration();
+
         public RecentTaskInfo() {
         }
 
@@ -1681,6 +1586,7 @@
             }
             dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
             dest.writeInt(resizeMode);
+            configuration.writeToParcel(dest, flags);
         }
 
         public void readFromParcel(Parcel source) {
@@ -1705,6 +1611,7 @@
                     Rect.CREATOR.createFromParcel(source) : null;
             supportsSplitScreenMultiWindow = source.readInt() == 1;
             resizeMode = source.readInt();
+            configuration.readFromParcel(source);
         }
 
         public static final Creator<RecentTaskInfo> CREATOR
@@ -1899,6 +1806,12 @@
          */
         public int resizeMode;
 
+        /**
+         * The full configuration the task is currently running in.
+         * @hide
+         */
+        final public Configuration configuration = new Configuration();
+
         public RunningTaskInfo() {
         }
 
@@ -1923,6 +1836,7 @@
             dest.writeInt(numRunning);
             dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
             dest.writeInt(resizeMode);
+            configuration.writeToParcel(dest, flags);
         }
 
         public void readFromParcel(Parcel source) {
@@ -1940,6 +1854,7 @@
             numRunning = source.readInt();
             supportsSplitScreenMultiWindow = source.readInt() != 0;
             resizeMode = source.readInt();
+            configuration.readFromParcel(source);
         }
 
         public static final Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
@@ -2122,6 +2037,73 @@
     }
 
     /**
+     * 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.
+     * @param windowingMode The windowing mode to set for the task.
+     * @param toTop If the task should be moved to the top once the windowing mode changes.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop)
+            throws SecurityException {
+        try {
+            getService().setTaskWindowingMode(taskId, windowingMode, toTop);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resizes the input stack id to the given bounds.
+     * @param stackId Id of the stack to resize.
+     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void resizeStack(int stackId, Rect bounds) throws SecurityException {
+        try {
+            getService().resizeStack(stackId, bounds, false /* allowResizeInDockedMode */,
+                    false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes stacks in the windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void removeStacksInWindowingModes(int[] windowingModes) throws SecurityException {
+        try {
+            getService().removeStacksInWindowingModes(windowingModes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes stack of the activity types from the system.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void removeStacksWithActivityTypes(int[] activityTypes) throws SecurityException {
+        try {
+            getService().removeStacksWithActivityTypes(activityTypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Represents a task snapshot.
      * @hide
      */
@@ -2599,6 +2581,11 @@
         public boolean visible;
         // Index of the stack in the display's stack list, can be used for comparison of stack order
         public int position;
+        /**
+         * The full configuration the stack is currently running in.
+         * @hide
+         */
+        final public Configuration configuration = new Configuration();
 
         @Override
         public int describeContents() {
@@ -2633,6 +2620,7 @@
             } else {
                 dest.writeInt(0);
             }
+            configuration.writeToParcel(dest, flags);
         }
 
         public void readFromParcel(Parcel source) {
@@ -2660,6 +2648,7 @@
             if (source.readInt() > 0) {
                 topActivity = ComponentName.readFromParcel(source);
             }
+            configuration.readFromParcel(source);
         }
 
         public static final Creator<StackInfo> CREATOR = new Creator<StackInfo>() {
@@ -2687,6 +2676,8 @@
                     sb.append(" displayId="); sb.append(displayId);
                     sb.append(" userId="); sb.append(userId);
                     sb.append("\n");
+                    sb.append(" configuration="); sb.append(configuration);
+                    sb.append("\n");
             prefix = prefix + "  ";
             for (int i = 0; i < taskIds.length; ++i) {
                 sb.append(prefix); sb.append("taskId="); sb.append(taskIds[i]);
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index 0bffc9e..a68c3a5 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -18,6 +18,8 @@
 
 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;
@@ -164,10 +166,16 @@
     private static final String KEY_LAUNCH_DISPLAY_ID = "android.activity.launchDisplayId";
 
     /**
-     * The stack id the activity should be launched into.
+     * The windowing mode the activity should be launched into.
      * @hide
      */
-    private static final String KEY_LAUNCH_STACK_ID = "android.activity.launchStackId";
+    private static final String KEY_LAUNCH_WINDOWING_MODE = "android.activity.windowingMode";
+
+    /**
+     * The activity type the activity should be launched as.
+     * @hide
+     */
+    private static final String KEY_LAUNCH_ACTIVITY_TYPE = "android.activity.activityType";
 
     /**
      * The task id the activity should be launched into.
@@ -272,7 +280,10 @@
     private int mExitCoordinatorIndex;
     private PendingIntent mUsageTimeReport;
     private int mLaunchDisplayId = INVALID_DISPLAY;
-    private int mLaunchStackId = INVALID_STACK_ID;
+    @WindowConfiguration.WindowingMode
+    private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
+    @WindowConfiguration.ActivityType
+    private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
     private int mLaunchTaskId = -1;
     private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
     private boolean mDisallowEnterPictureInPictureWhileLaunching;
@@ -860,7 +871,8 @@
                 break;
         }
         mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
-        mLaunchStackId = opts.getInt(KEY_LAUNCH_STACK_ID, INVALID_STACK_ID);
+        mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
+        mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
         mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
         mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
         mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
@@ -1070,14 +1082,34 @@
     }
 
     /** @hide */
-    public int getLaunchStackId() {
-        return mLaunchStackId;
+    public int getLaunchWindowingMode() {
+        return mLaunchWindowingMode;
+    }
+
+    /**
+     * Sets the windowing mode the activity should launch into. If the input windowing mode is
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} and the device
+     * isn't currently in split-screen windowing mode, then the activity will be launched in
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN} windowing mode. For clarity
+     * on this you can use
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY}
+     *
+     * @hide
+     */
+    @TestApi
+    public void setLaunchWindowingMode(int windowingMode) {
+        mLaunchWindowingMode = windowingMode;
+    }
+
+    /** @hide */
+    public int getLaunchActivityType() {
+        return mLaunchActivityType;
     }
 
     /** @hide */
     @TestApi
-    public void setLaunchStackId(int launchStackId) {
-        mLaunchStackId = launchStackId;
+    public void setLaunchActivityType(int activityType) {
+        mLaunchActivityType = activityType;
     }
 
     /**
@@ -1291,7 +1323,8 @@
                 break;
         }
         b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
-        b.putInt(KEY_LAUNCH_STACK_ID, mLaunchStackId);
+        b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
+        b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
         b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
         b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
         b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 4e8d240..2516a3e 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -5281,7 +5281,7 @@
                                 final ApplicationInfo aInfo =
                                         sPackageManager.getApplicationInfo(
                                                 packageName,
-                                                0 /*flags*/,
+                                                PackageManager.GET_SHARED_LIBRARY_FILES,
                                                 UserHandle.myUserId());
 
                                 if (mActivities.size() > 0) {
@@ -5780,7 +5780,7 @@
                 final int preloadedFontsResource = info.metaData.getInt(
                         ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
                 if (preloadedFontsResource != 0) {
-                    data.info.mResources.preloadFonts(preloadedFontsResource);
+                    data.info.getResources().preloadFonts(preloadedFontsResource);
                 }
             }
         } catch (RemoteException e) {
diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java
index c48be77..5f34322 100644
--- a/android/app/ContextImpl.java
+++ b/android/app/ContextImpl.java
@@ -74,6 +74,7 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
@@ -185,6 +186,8 @@
     // The name of the split this Context is representing. May be null.
     private @Nullable String mSplitName = null;
 
+    private AutofillClient mAutofillClient = null;
+
     private final Object mSync = new Object();
 
     @GuardedBy("mSync")
@@ -2225,6 +2228,18 @@
         return mUser.getIdentifier();
     }
 
+    /** @hide */
+    @Override
+    public AutofillClient getAutofillClient() {
+        return mAutofillClient;
+    }
+
+    /** @hide */
+    @Override
+    public void setAutofillClient(AutofillClient client) {
+        mAutofillClient = client;
+    }
+
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java
index 76643d6..54f74b1 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -174,7 +174,7 @@
      */
     public Intent createConfirmFactoryResetCredentialIntent(
             CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
-        if (!LockPatternUtils.frpCredentialEnabled()) {
+        if (!LockPatternUtils.frpCredentialEnabled(mContext)) {
             Log.w(TAG, "Factory reset credentials not supported.");
             return null;
         }
diff --git a/android/app/Notification.java b/android/app/Notification.java
index ee6c1cb..fee7d6c 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -67,7 +67,6 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -3840,8 +3839,8 @@
                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
                 if (isColorized()) {
-                    contentView.setDrawableParameters(R.id.profile_badge, false, -1,
-                            getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1);
+                    contentView.setDrawableTint(R.id.profile_badge, false,
+                            getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP);
                 }
             }
         }
@@ -3906,7 +3905,6 @@
             if (p.title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
                 contentView.setTextViewText(R.id.title, processTextSpans(p.title));
-                updateTextSizePrimary(contentView, R.id.title);
                 if (!p.ambient) {
                     setTextViewColorPrimary(contentView, R.id.title);
                 }
@@ -3918,7 +3916,6 @@
                 int textId = showProgress ? com.android.internal.R.id.text_line_1
                         : com.android.internal.R.id.text;
                 contentView.setTextViewText(textId, processTextSpans(p.text));
-                updateTextSizeSecondary(contentView, textId);
                 if (!p.ambient) {
                     setTextViewColorSecondary(contentView, textId);
                 }
@@ -3930,25 +3927,6 @@
             return contentView;
         }
 
-        private void updateTextSizeSecondary(RemoteViews contentView, int textId) {
-            updateTextSizeColorized(contentView, textId,
-                    com.android.internal.R.dimen.notification_text_size_colorized,
-                    com.android.internal.R.dimen.notification_text_size);
-        }
-
-        private void updateTextSizePrimary(RemoteViews contentView, int textId) {
-            updateTextSizeColorized(contentView, textId,
-                    com.android.internal.R.dimen.notification_title_text_size_colorized,
-                    com.android.internal.R.dimen.notification_title_text_size);
-        }
-
-        private void updateTextSizeColorized(RemoteViews contentView, int textId,
-                int colorizedDimen, int normalDimen) {
-            int size = mContext.getResources().getDimensionPixelSize(isColorized()
-                    ? colorizedDimen : normalDimen);
-            contentView.setTextViewTextSize(textId, TypedValue.COMPLEX_UNIT_PX, size);
-        }
-
         private CharSequence processTextSpans(CharSequence text) {
             if (hasForegroundColor()) {
                 return NotificationColorUtil.clearColorSpans(text);
@@ -4152,18 +4130,14 @@
 
                 if (action != null) {
                     int contrastColor = resolveContrastColor();
-                    contentView.setDrawableParameters(R.id.reply_icon_action,
+                    contentView.setDrawableTint(R.id.reply_icon_action,
                             true /* targetBackground */,
-                            -1,
-                            contrastColor,
-                            PorterDuff.Mode.SRC_ATOP, -1);
+                            contrastColor, PorterDuff.Mode.SRC_ATOP);
                     int iconColor = NotificationColorUtil.isColorLight(contrastColor)
                             ? Color.BLACK : Color.WHITE;
-                    contentView.setDrawableParameters(R.id.reply_icon_action,
+                    contentView.setDrawableTint(R.id.reply_icon_action,
                             false /* targetBackground */,
-                            -1,
-                            iconColor,
-                            PorterDuff.Mode.SRC_ATOP, -1);
+                            iconColor, PorterDuff.Mode.SRC_ATOP);
                     contentView.setOnClickPendingIntent(R.id.right_icon,
                             action.actionIntent);
                     contentView.setOnClickPendingIntent(R.id.reply_icon_action,
@@ -4207,8 +4181,8 @@
 
         private void bindExpandButton(RemoteViews contentView) {
             int color = getPrimaryHighlightColor();
-            contentView.setDrawableParameters(R.id.expand_button, false, -1, color,
-                    PorterDuff.Mode.SRC_ATOP, -1);
+            contentView.setDrawableTint(R.id.expand_button, false, color,
+                    PorterDuff.Mode.SRC_ATOP);
             contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
                     color);
         }
@@ -4315,8 +4289,7 @@
                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
             }
             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
-            contentView.setDrawableParameters(R.id.icon, false /* targetBackground */,
-                    -1 /* alpha */, -1 /* colorFilter */, null /* mode */, mN.iconLevel);
+            contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
             processSmallIconColor(mN.mSmallIcon, contentView, ambient);
         }
 
@@ -4706,8 +4679,8 @@
                     bgColor = mContext.getColor(oddAction ? R.color.notification_action_list
                             : R.color.notification_action_list_dark);
                 }
-                button.setDrawableParameters(R.id.button_holder, true, -1, bgColor,
-                        PorterDuff.Mode.SRC_ATOP, -1);
+                button.setDrawableTint(R.id.button_holder, true,
+                        bgColor, PorterDuff.Mode.SRC_ATOP);
                 CharSequence title = action.title;
                 ColorStateList[] outResultColor = null;
                 if (isLegacy()) {
@@ -4840,8 +4813,8 @@
             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
             int color = ambient ? resolveAmbientColor() : getPrimaryHighlightColor();
             if (colorable) {
-                contentView.setDrawableParameters(R.id.icon, false, -1, color,
-                        PorterDuff.Mode.SRC_ATOP, -1);
+                contentView.setDrawableTint(R.id.icon, false, color,
+                        PorterDuff.Mode.SRC_ATOP);
 
             }
             contentView.setInt(R.id.notification_header, "setOriginalIconColor",
@@ -4857,8 +4830,8 @@
             if (largeIcon != null && isLegacy()
                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                 // resolve color will fall back to the default when legacy
-                contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(),
-                        PorterDuff.Mode.SRC_ATOP, -1);
+                contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(),
+                        PorterDuff.Mode.SRC_ATOP);
             }
         }
 
@@ -5874,7 +5847,6 @@
             builder.setTextViewColorSecondary(contentView, R.id.big_text);
             contentView.setViewVisibility(R.id.big_text,
                     TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
-            builder.updateTextSizeSecondary(contentView, R.id.big_text);
             contentView.setBoolean(R.id.big_text, "setHasImage", builder.mN.hasLargeIcon());
         }
     }
@@ -6208,7 +6180,6 @@
                 contentView.setViewVisibility(rowId, View.VISIBLE);
                 contentView.setTextViewText(rowId, mBuilder.processTextSpans(
                         makeMessageLine(m, mBuilder)));
-                mBuilder.updateTextSizeSecondary(contentView, rowId);
                 mBuilder.setTextViewColorSecondary(contentView, rowId);
 
                 if (contractedMessage == m) {
@@ -6576,7 +6547,6 @@
                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
                     contentView.setTextViewText(rowIds[i],
                             mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
-                    mBuilder.updateTextSizeSecondary(contentView, rowIds[i]);
                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
                     handleInboxImageMargin(contentView, rowIds[i], first);
@@ -6775,8 +6745,8 @@
                     : NotificationColorUtil.resolveColor(mBuilder.mContext,
                             Notification.COLOR_DEFAULT);
 
-            button.setDrawableParameters(R.id.action0, false, -1, tintColor,
-                    PorterDuff.Mode.SRC_ATOP, -1);
+            button.setDrawableTint(R.id.action0, false, tintColor,
+                    PorterDuff.Mode.SRC_ATOP);
             if (!tombstone) {
                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
             }
diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java
index 163a8dc..47063f0 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -15,8 +15,11 @@
  */
 package android.app;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.NotificationManager.Importance;
+import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.media.AudioAttributes;
 import android.net.Uri;
@@ -25,6 +28,9 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -135,12 +141,15 @@
     private boolean mLights;
     private int mLightColor = DEFAULT_LIGHT_COLOR;
     private long[] mVibration;
+    // Bitwise representation of fields that have been changed by the user, preventing the app from
+    // making changes to these fields.
     private int mUserLockedFields;
     private boolean mVibrationEnabled;
     private boolean mShowBadge = DEFAULT_SHOW_BADGE;
     private boolean mDeleted = DEFAULT_DELETED;
     private String mGroup;
     private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
+    // If this is a blockable system notification channel.
     private boolean mBlockableSystem = false;
 
     /**
@@ -565,14 +574,35 @@
     /**
      * @hide
      */
+    public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
+        populateFromXml(parser, true, context);
+    }
+
+    /**
+     * @hide
+     */
     @SystemApi
     public void populateFromXml(XmlPullParser parser) {
+        populateFromXml(parser, false, null);
+    }
+
+    /**
+     * If {@param forRestore} is true, {@param Context} MUST be non-null.
+     */
+    private void populateFromXml(XmlPullParser parser, boolean forRestore,
+            @Nullable Context context) {
+        Preconditions.checkArgument(!forRestore || context != null,
+                "forRestore is true but got null context");
+
         // Name, id, and importance are set in the constructor.
         setDescription(parser.getAttributeValue(null, ATT_DESC));
         setBypassDnd(Notification.PRIORITY_DEFAULT
                 != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
-        setSound(safeUri(parser, ATT_SOUND), safeAudioAttributes(parser));
+
+        Uri sound = safeUri(parser, ATT_SOUND);
+        setSound(forRestore ? restoreSoundUri(context, sound) : sound, safeAudioAttributes(parser));
+
         enableLights(safeBool(parser, ATT_LIGHTS, false));
         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
         setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
@@ -584,11 +614,62 @@
         setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
     }
 
+    @Nullable
+    private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
+        if (uri == null) {
+            return null;
+        }
+        ContentResolver contentResolver = context.getContentResolver();
+        // There are backups out there with uncanonical uris (because we fixed this after
+        // shipping). If uncanonical uris are given to MediaProvider.uncanonicalize it won't
+        // verify the uri against device storage and we'll possibly end up with a broken uri.
+        // We then canonicalize the uri to uncanonicalize it back, which means we properly check
+        // the uri and in the case of not having the resource we end up with the default - better
+        // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
+        // according to the docs because canonicalize method has to handle canonical uris as well.
+        Uri canonicalizedUri = contentResolver.canonicalize(uri);
+        if (canonicalizedUri == null) {
+            // We got a null because the uri in the backup does not exist here, so we return default
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        }
+        return contentResolver.uncanonicalize(canonicalizedUri);
+    }
+
     /**
      * @hide
      */
     @SystemApi
     public void writeXml(XmlSerializer out) throws IOException {
+        writeXml(out, false, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void writeXmlForBackup(XmlSerializer out, Context context) throws IOException {
+        writeXml(out, true, context);
+    }
+
+    private Uri getSoundForBackup(Context context) {
+        Uri sound = getSound();
+        if (sound == null) {
+            return null;
+        }
+        Uri canonicalSound = context.getContentResolver().canonicalize(sound);
+        if (canonicalSound == null) {
+            // The content provider does not support canonical uris so we backup the default
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        }
+        return canonicalSound;
+    }
+
+    /**
+     * If {@param forBackup} is true, {@param Context} MUST be non-null.
+     */
+    private void writeXml(XmlSerializer out, boolean forBackup, @Nullable Context context)
+            throws IOException {
+        Preconditions.checkArgument(!forBackup || context != null,
+                "forBackup is true but got null context");
         out.startTag(null, TAG_CHANNEL);
         out.attribute(null, ATT_ID, getId());
         if (getName() != null) {
@@ -609,8 +690,9 @@
             out.attribute(null, ATT_VISIBILITY,
                     Integer.toString(getLockscreenVisibility()));
         }
-        if (getSound() != null) {
-            out.attribute(null, ATT_SOUND, getSound().toString());
+        Uri sound = forBackup ? getSoundForBackup(context) : getSound();
+        if (sound != null) {
+            out.attribute(null, ATT_SOUND, sound.toString());
         }
         if (getAudioAttributes() != null) {
             out.attribute(null, ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
@@ -850,4 +932,35 @@
                 + ", mBlockableSystem=" + mBlockableSystem
                 + '}';
     }
+
+    /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(NotificationChannelProto.ID, mId);
+        proto.write(NotificationChannelProto.NAME, mName);
+        proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
+        proto.write(NotificationChannelProto.IMPORTANCE, mImportance);
+        proto.write(NotificationChannelProto.CAN_BYPASS_DND, mBypassDnd);
+        proto.write(NotificationChannelProto.LOCKSCREEN_VISIBILITY, mLockscreenVisibility);
+        if (mSound != null) {
+            proto.write(NotificationChannelProto.SOUND, mSound.toString());
+        }
+        proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
+        proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
+        if (mVibration != null) {
+            for (long v : mVibration) {
+                proto.write(NotificationChannelProto.VIBRATION, v);
+            }
+        }
+        proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
+        proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
+        proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
+        proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
+        proto.write(NotificationChannelProto.GROUP, mGroup);
+        if (mAudioAttributes != null) {
+            long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES);
+            mAudioAttributes.toProto(proto);
+            proto.end(aToken);
+        }
+        proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
+    }
 }
diff --git a/android/app/NotificationChannelGroup.java b/android/app/NotificationChannelGroup.java
index 5173311..5cb7fb7 100644
--- a/android/app/NotificationChannelGroup.java
+++ b/android/app/NotificationChannelGroup.java
@@ -21,6 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -295,4 +296,15 @@
                 + ", mChannels=" + mChannels
                 + '}';
     }
+
+    /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(NotificationChannelGroupProto.ID, mId);
+        proto.write(NotificationChannelGroupProto.NAME, mName.toString());
+        proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
+        proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
+        for (NotificationChannel channel : mChannels) {
+            channel.toProto(proto);
+        }
+    }
 }
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index 8fa7d6c..eb52cb7 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -41,6 +41,7 @@
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1061,6 +1062,27 @@
                     + "]";
         }
 
+        /** @hide */
+        public void toProto(ProtoOutputStream proto, long fieldId) {
+            final long pToken = proto.start(fieldId);
+
+            bitwiseToProtoEnum(proto, PolicyProto.PRIORITY_CATEGORIES, priorityCategories);
+            proto.write(PolicyProto.PRIORITY_CALL_SENDER, priorityCallSenders);
+            proto.write(PolicyProto.PRIORITY_MESSAGE_SENDER, priorityMessageSenders);
+            bitwiseToProtoEnum(
+                    proto, PolicyProto.SUPPRESSED_VISUAL_EFFECTS, suppressedVisualEffects);
+
+            proto.end(pToken);
+        }
+
+        private static void bitwiseToProtoEnum(ProtoOutputStream proto, long fieldId, int data) {
+            for (int i = 1; data > 0; ++i, data >>>= 1) {
+                if ((data & 1) == 1) {
+                    proto.write(fieldId, i);
+                }
+            }
+        }
+
         public static String suppressedEffectsToString(int effects) {
             if (effects <= 0) return "";
             final StringBuilder sb = new StringBuilder();
diff --git a/android/app/SharedElementCallback.java b/android/app/SharedElementCallback.java
index af13e69..80fb805 100644
--- a/android/app/SharedElementCallback.java
+++ b/android/app/SharedElementCallback.java
@@ -27,6 +27,7 @@
 import android.os.Parcelable;
 import android.transition.TransitionUtils;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 
@@ -176,7 +177,7 @@
             Drawable d = imageView.getDrawable();
             Drawable bg = imageView.getBackground();
             if (d != null && (bg == null || bg.getAlpha() == 0)) {
-                Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
+                Bitmap bitmap = TransitionUtils.createDrawableBitmap(d, imageView);
                 if (bitmap != null) {
                     Bundle bundle = new Bundle();
                     if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
@@ -202,7 +203,8 @@
         } else {
             mTempMatrix.set(viewToGlobalMatrix);
         }
-        return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
+        ViewGroup parent = (ViewGroup) sharedElement.getParent();
+        return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds, parent);
     }
 
     /**
diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java
index 4a09214..8987bc0 100644
--- a/android/app/StatusBarManager.java
+++ b/android/app/StatusBarManager.java
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-
 package android.app;
 
 import android.annotation.IntDef;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.Binder;
-import android.os.RemoteException;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Slog;
 import android.view.View;
@@ -71,14 +70,18 @@
      * Setting this flag disables quick settings completely, but does not disable expanding the
      * notification shade.
      */
-    public static final int DISABLE2_QUICK_SETTINGS = 0x00000001;
+    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_NONE = 0x00000000;
 
-    public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS;
+    public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS
+            | DISABLE2_NOTIFICATION_SHADE;
 
     @IntDef(flag = true,
-            value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS})
+            value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS,
+                    DISABLE2_NOTIFICATION_SHADE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Disable2Flags {}
 
diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java
index ab70f0e..50f1f36 100644
--- a/android/app/SystemServiceRegistry.java
+++ b/android/app/SystemServiceRegistry.java
@@ -81,10 +81,10 @@
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
 import android.net.wifi.IRttManager;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.IWifiScanner;
@@ -95,6 +95,8 @@
 import android.net.wifi.aware.WifiAwareManager;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.WifiRttManager;
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -603,6 +605,16 @@
                         ConnectivityThread.getInstanceLooper());
             }});
 
+        registerService(Context.WIFI_RTT2_SERVICE, WifiRttManager.class,
+                new CachedServiceFetcher<WifiRttManager>() {
+                    @Override
+                    public WifiRttManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT2_SERVICE);
+                        IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+                        return new WifiRttManager(ctx.getOuterContext(), service);
+                    }});
+
         registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
                 new CachedServiceFetcher<EthernetManager>() {
             @Override
diff --git a/android/app/TaskStackListener.java b/android/app/TaskStackListener.java
index a52ca0a..402e209 100644
--- a/android/app/TaskStackListener.java
+++ b/android/app/TaskStackListener.java
@@ -31,7 +31,8 @@
     }
 
     @Override
-    public void onActivityPinned(String packageName, int taskId) throws RemoteException {
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
+            throws RemoteException {
     }
 
     @Override
diff --git a/android/app/VrManager.java b/android/app/VrManager.java
index 363e20a..5c6ffa3 100644
--- a/android/app/VrManager.java
+++ b/android/app/VrManager.java
@@ -4,6 +4,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -62,7 +63,10 @@
      * @param callback The callback to register.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.RESTRICTED_VR_ACCESS,
+            android.Manifest.permission.ACCESS_VR_STATE
+    })
     public void registerVrStateCallback(VrStateCallback callback, @NonNull Handler handler) {
         if (callback == null || mCallbackMap.containsKey(callback)) {
             return;
@@ -88,7 +92,10 @@
      * @param callback The callback to deregister.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.RESTRICTED_VR_ACCESS,
+            android.Manifest.permission.ACCESS_VR_STATE
+    })
     public void unregisterVrStateCallback(VrStateCallback callback) {
         CallbackEntry entry = mCallbackMap.remove(callback);
         if (entry != null) {
@@ -110,7 +117,10 @@
      * Returns the current VrMode state.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.RESTRICTED_VR_ACCESS,
+            android.Manifest.permission.ACCESS_VR_STATE
+    })
     public boolean getVrModeEnabled() {
         try {
             return mService.getVrModeState();
@@ -124,7 +134,10 @@
      * Returns the current VrMode state.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.RESTRICTED_VR_ACCESS,
+            android.Manifest.permission.ACCESS_VR_STATE
+    })
     public boolean getPersistentVrModeEnabled() {
         try {
             return mService.getPersistentVrModeEnabled();
@@ -169,4 +182,20 @@
             e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set the component name of the compositor service to bind.
+     *
+     * @param componentName ComponentName of a Service in the application's compositor process to
+     * bind to, or null to clear the current binding.
+     */
+    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    public void setAndBindVrCompositor(ComponentName componentName) {
+        try {
+            mService.setAndBindCompositor(
+                    (componentName == null) ? null : componentName.flattenToString());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 5d87e1c..6b40538 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -17,6 +17,9 @@
 package android.app;
 
 import static android.app.ActivityThread.isSystem;
+import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
+import static android.app.WindowConfigurationProto.APP_BOUNDS;
+import static android.app.WindowConfigurationProto.WINDOWING_MODE;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -25,6 +28,7 @@
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 
 /**
@@ -48,26 +52,33 @@
     /** The current windowing mode of the configuration. */
     private @WindowingMode int mWindowingMode;
 
-    /** Windowing mode is currently not defined.
-     * @hide */
+    /** Windowing mode is currently not defined. */
     public static final int WINDOWING_MODE_UNDEFINED = 0;
-    /** Occupies the full area of the screen or the parent container.
-     * @hide */
+    /** Occupies the full area of the screen or the parent container. */
     public static final int WINDOWING_MODE_FULLSCREEN = 1;
-    /** Always on-top (always visible). of other siblings in its parent container.
-     * @hide */
+    /** Always on-top (always visible). of other siblings in its parent container. */
     public static final int WINDOWING_MODE_PINNED = 2;
-    /** The primary container driving the screen to be in split-screen mode.
-     * @hide */
+    /** The primary container driving the screen to be in split-screen mode. */
     public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
     /**
      * The containers adjacent to the {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} container in
      * split-screen mode.
-     * @hide
+     * NOTE: Containers launched with the windowing mode with APIs like
+     * {@link ActivityOptions#setLaunchWindowingMode(int)} will be launched in
+     * {@link #WINDOWING_MODE_FULLSCREEN} if the display isn't currently in split-screen windowing
+     * mode
+     * @see #WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY
      */
     public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
-    /** Can be freely resized within its parent container.
-     * @hide */
+    /**
+     * Alias for {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} that makes it clear that the usage
+     * points for APIs like {@link ActivityOptions#setLaunchWindowingMode(int)} that the container
+     * will launch into fullscreen or split-screen secondary depending on if the device is currently
+     * in fullscreen mode or split-screen mode.
+     */
+    public static final int WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY =
+            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+    /** Can be freely resized within its parent container. */
     public static final int WINDOWING_MODE_FREEFORM = 5;
 
     /** @hide */
@@ -77,6 +88,7 @@
             WINDOWING_MODE_PINNED,
             WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
             WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+            WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY,
             WINDOWING_MODE_FREEFORM,
     })
     public @interface WindowingMode {}
@@ -84,18 +96,15 @@
     /** The current activity type of the configuration. */
     private @ActivityType int mActivityType;
 
-    /** Activity type is currently not defined.
-     * @hide */
+    /** Activity type is currently not defined. */
     public static final int ACTIVITY_TYPE_UNDEFINED = 0;
-    /** Standard activity type. Nothing special about the activity...
-     * @hide */
+    /** Standard activity type. Nothing special about the activity... */
     public static final int ACTIVITY_TYPE_STANDARD = 1;
     /** Home/Launcher activity type. */
     public static final int ACTIVITY_TYPE_HOME = 2;
     /** Recents/Overview activity type. */
     public static final int ACTIVITY_TYPE_RECENTS = 3;
-    /** Assistant activity type.
-     * @hide */
+    /** Assistant activity type. */
     public static final int ACTIVITY_TYPE_ASSISTANT = 4;
 
     /** @hide */
@@ -127,7 +136,6 @@
             })
     public @interface WindowConfig {}
 
-    /** @hide */
     public WindowConfiguration() {
         unset();
     }
@@ -176,7 +184,6 @@
      * Set {@link #mAppBounds} to the input Rect.
      * @param rect The rect value to set {@link #mAppBounds} to.
      * @see #getAppBounds()
-     * @hide
      */
     public void setAppBounds(Rect rect) {
         if (rect == null) {
@@ -200,26 +207,20 @@
         mAppBounds.set(left, top, right, bottom);
     }
 
-    /**
-     * @see #setAppBounds(Rect)
-     * @hide
-     */
+    /** @see #setAppBounds(Rect) */
     public Rect getAppBounds() {
         return mAppBounds;
     }
 
-    /** @hide */
     public void setWindowingMode(@WindowingMode int windowingMode) {
         mWindowingMode = windowingMode;
     }
 
-    /** @hide */
     @WindowingMode
     public int getWindowingMode() {
         return mWindowingMode;
     }
 
-    /** @hide */
     public void setActivityType(@ActivityType int activityType) {
         if (mActivityType == activityType) {
             return;
@@ -237,13 +238,11 @@
         mActivityType = activityType;
     }
 
-    /** @hide */
     @ActivityType
     public int getActivityType() {
         return mActivityType;
     }
 
-    /** @hide */
     public void setTo(WindowConfiguration other) {
         setAppBounds(other.mAppBounds);
         setWindowingMode(other.mWindowingMode);
@@ -382,6 +381,24 @@
     }
 
     /**
+     * Write to a protocol buffer output stream.
+     * Protocol buffer message definition at {@link android.app.WindowConfigurationProto}
+     *
+     * @param protoOutputStream Stream to write the WindowConfiguration object to.
+     * @param fieldId           Field Id of the WindowConfiguration as defined in the parent message
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        if (mAppBounds != null) {
+            mAppBounds.writeToProto(protoOutputStream, APP_BOUNDS);
+        }
+        protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
+        protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
+        protoOutputStream.end(token);
+    }
+
+    /**
      * Returns true if the activities associated with this window configuration display a shadow
      * around their border.
      * @hide
@@ -483,10 +500,15 @@
      * @hide
      */
     public boolean supportSplitScreenWindowingMode() {
-        if (mActivityType == ACTIVITY_TYPE_ASSISTANT) {
+        return supportSplitScreenWindowingMode(mWindowingMode, mActivityType);
+    }
+
+    /** @hide */
+    public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) {
+        if (activityType == ACTIVITY_TYPE_ASSISTANT) {
             return false;
         }
-        return mWindowingMode != WINDOWING_MODE_FREEFORM && mWindowingMode != WINDOWING_MODE_PINNED;
+        return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
     }
 
     private static String windowingModeToString(@WindowingMode int windowingMode) {
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 6bccad9..3c53063 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -63,7 +63,9 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.org.conscrypt.TrustedCertificateStore;
 
 import java.io.ByteArrayInputStream;
@@ -3142,6 +3144,7 @@
      */
     public static final int WIPE_EUICC = 0x0004;
 
+
     /**
      * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
      * other users will remain unaffected. Calling from the primary user will cause the device to
@@ -3157,10 +3160,47 @@
      *             that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
      */
     public void wipeData(int flags) {
-        throwIfParentInstance("wipeData");
+        final String wipeReasonForUser = mContext.getString(
+                R.string.work_profile_deleted_description_dpm_wipe);
+        wipeDataInternal(flags, wipeReasonForUser);
+    }
+
+    /**
+     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
+     * other users will remain unaffected, the provided reason for wiping data can be shown to
+     * user. Calling from the primary user will cause the device to reboot, erasing all device data
+     * - including all the secondary users and their data - while booting up. In this case, we don't
+     * show the reason to the user since the device would be factory reset.
+     * <p>
+     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
+     * be able to call this method; if it has not, a security exception will be thrown.
+     *
+     * @param flags Bit mask of additional options: currently supported flags are
+     *            {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+     * @param reason a string that contains the reason for wiping data, which can be
+     *                          presented to the user.
+     * @throws SecurityException if the calling application does not own an active administrator
+     *             that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+     * @throws IllegalArgumentException if the input reason string is null or empty.
+     */
+    public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
+        Preconditions.checkNotNull(reason, "CharSequence is null");
+        wipeDataInternal(flags, reason.toString());
+    }
+
+    /**
+     * Internal function for both {@link #wipeData(int)} and
+     * {@link #wipeDataWithReason(int, CharSequence)} to call.
+     *
+     * @see #wipeData(int)
+     * @see #wipeDataWithReason(int, CharSequence)
+     * @hide
+     */
+    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
+        throwIfParentInstance("wipeDataWithReason");
         if (mService != null) {
             try {
-                mService.wipeData(flags);
+                mService.wipeDataWithReason(flags, wipeReasonForUser);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -7534,12 +7574,12 @@
     }
 
     /**
-     * Called by the device owner or profile owner to set the name of the organization under
-     * management.
-     * <p>
-     * If the organization name needs to be localized, it is the responsibility of the
-     * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
-     * and set a new version of this string accordingly.
+     * Called by the device owner (since API 26) or profile owner (since API 24) to set the name of
+     * the organization under management.
+     *
+     * <p>If the organization name needs to be localized, it is the responsibility of the {@link
+     * DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set
+     * a new version of this string accordingly.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param title The organization name or {@code null} to clear a previously set name.
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index 55c22de..d9b7cd7 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -674,6 +674,7 @@
 
         ViewNodeText mText;
         int mInputType;
+        String mWebScheme;
         String mWebDomain;
         Bundle mExtras;
         LocaleList mLocaleList;
@@ -751,6 +752,7 @@
                 mInputType = in.readInt();
             }
             if ((flags&FLAGS_HAS_URL) != 0) {
+                mWebScheme = in.readString();
                 mWebDomain = in.readString();
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -813,7 +815,7 @@
             if (mInputType != 0) {
                 flags |= FLAGS_HAS_INPUT_TYPE;
             }
-            if (mWebDomain != null) {
+            if (mWebScheme != null || mWebDomain != null) {
                 flags |= FLAGS_HAS_URL;
             }
             if (mLocaleList != null) {
@@ -908,6 +910,7 @@
                 out.writeInt(mInputType);
             }
             if ((flags&FLAGS_HAS_URL) != 0) {
+                out.writeString(mWebScheme);
                 out.writeString(mWebDomain);
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -1260,18 +1263,31 @@
          * <p>Typically used when the view associated with the view is a container for an HTML
          * document.
          *
-         * <strong>WARNING:</strong> a {@link android.service.autofill.AutofillService} should only
-         * use this domain for autofill purposes when it trusts the app generating it (i.e., the app
-         * defined by {@link AssistStructure#getActivityComponent()}).
+         * <p><b>Warning:</b> an autofill service cannot trust the value reported by this method
+         * without verifing its authenticity&mdash;see the "Web security" section of
+         * {@link android.service.autofill.AutofillService} for more details.
          *
          * @return domain-only part of the document. For example, if the full URL is
-         * {@code http://my.site/login?user=my_user}, it returns {@code my.site}.
+         * {@code https://example.com/login?user=my_user}, it returns {@code example.com}.
          */
         @Nullable public String getWebDomain() {
             return mWebDomain;
         }
 
         /**
+         * Returns the scheme of the HTML document represented by this view.
+         *
+         * <p>Typically used when the view associated with the view is a container for an HTML
+         * document.
+         *
+         * @return scheme-only part of the document. For example, if the full URL is
+         * {@code https://example.com/login?user=my_user}, it returns {@code https}.
+         */
+        @Nullable public String getWebScheme() {
+            return mWebScheme;
+        }
+
+        /**
          * Returns the HTML properties associated with this view.
          *
          * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
@@ -1767,10 +1783,13 @@
         @Override
         public void setWebDomain(@Nullable String domain) {
             if (domain == null) {
+                mNode.mWebScheme = null;
                 mNode.mWebDomain = null;
                 return;
             }
-            mNode.mWebDomain = Uri.parse(domain).getHost();
+            Uri uri = Uri.parse(domain);
+            mNode.mWebScheme = uri.getScheme();
+            mNode.mWebDomain = uri.getHost();
         }
 
         @Override
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index 87e516c..1434c9b 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -317,7 +317,8 @@
     }
 
     /**
-     * Whether this job needs the device to be plugged in.
+     * Whether this job requires that the device be charging (or be a non-battery-powered
+     * device connected to permanent power, such as Android TV devices).
      */
     public boolean isRequireCharging() {
         return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
@@ -331,7 +332,10 @@
     }
 
     /**
-     * Whether this job needs the device to be in an Idle maintenance window.
+     * Whether this job requires that the user <em>not</em> be interacting with the device.
+     *
+     * <p class="note">This is <em>not</em> the same as "doze" or "device idle";
+     * it is purely about the user's direct interactions.</p>
      */
     public boolean isRequireDeviceIdle() {
         return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
@@ -918,9 +922,19 @@
         }
 
         /**
-         * Specify that to run this job, the device needs to be plugged in. This defaults to
-         * false.
-         * @param requiresCharging Whether or not the device is plugged in.
+         * Specify that to run this job, the device must be charging (or be a
+         * non-battery-powered device connected to permanent power, such as Android TV
+         * devices). This defaults to {@code false}.
+         *
+         * <p class="note">For purposes of running jobs, a battery-powered device
+         * "charging" is not quite the same as simply being connected to power.  If the
+         * device is so busy that the battery is draining despite a power connection, jobs
+         * with this constraint will <em>not</em> run.  This can happen during some
+         * common use cases such as video chat, particularly if the device is plugged in
+         * to USB rather than to wall power.
+         *
+         * @param requiresCharging Pass {@code true} to require that the device be
+         *     charging in order to run the job.
          */
         public Builder setRequiresCharging(boolean requiresCharging) {
             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING)
@@ -942,14 +956,22 @@
         }
 
         /**
-         * Specify that to run, the job needs the device to be in idle mode. This defaults to
-         * false.
-         * <p>Idle mode is a loose definition provided by the system, which means that the device
-         * is not in use, and has not been in use for some time. As such, it is a good time to
-         * perform resource heavy jobs. Bear in mind that battery usage will still be attributed
-         * to your application, and surfaced to the user in battery stats.</p>
-         * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
-         *                           window.
+         * When set {@code true}, ensure that this job will not run if the device is in active use.
+         * The default state is {@code false}: that is, the for the job to be runnable even when
+         * someone is interacting with the device.
+         *
+         * <p>This state is a loose definition provided by the system. In general, it means that
+         * the device is not currently being used interactively, and has not been in use for some
+         * time. As such, it is a good time to perform resource heavy jobs. Bear in mind that
+         * battery usage will still be attributed to your application, and surfaced to the user in
+         * battery stats.</p>
+         *
+         * <p class="note">Despite the similar naming, this job constraint is <em>not</em>
+         * related to the system's "device idle" or "doze" states.  This constraint only
+         * determines whether a job is allowed to run while the device is directly in use.
+         *
+         * @param requiresDeviceIdle Pass {@code true} to prevent the job from running
+         *     while the device is being used interactively.
          */
         public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE)
diff --git a/android/app/timezone/RulesUpdaterContract.java b/android/app/timezone/RulesUpdaterContract.java
index 9c62f46..74ed658 100644
--- a/android/app/timezone/RulesUpdaterContract.java
+++ b/android/app/timezone/RulesUpdaterContract.java
@@ -51,7 +51,7 @@
      * applies.
      */
     public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
-            "android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
+            "com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
 
     /**
      * The extra containing the {@code byte[]} that should be passed to
@@ -61,7 +61,7 @@
      * {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
      */
     public static final String EXTRA_CHECK_TOKEN =
-            "android.intent.extra.timezone.CHECK_TOKEN";
+            "com.android.intent.extra.timezone.CHECK_TOKEN";
 
     /**
      * Creates an intent that would trigger a time zone rules update check.
@@ -83,8 +83,7 @@
         Intent intent = createUpdaterIntent(updaterAppPackageName);
         intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
         context.sendBroadcastAsUser(
-                intent,
-                UserHandle.of(UserHandle.myUserId()),
+                intent, UserHandle.SYSTEM,
                 RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
     }
 }
diff --git a/android/appwidget/AppWidgetHostView.java b/android/appwidget/AppWidgetHostView.java
index 8a1eae2..dc9970a 100644
--- a/android/appwidget/AppWidgetHostView.java
+++ b/android/appwidget/AppWidgetHostView.java
@@ -23,17 +23,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -58,17 +53,17 @@
  * {@link RemoteViews}.
  */
 public class AppWidgetHostView extends FrameLayout {
+
     static final String TAG = "AppWidgetHostView";
+    private static final String KEY_JAILED_ARRAY = "jail";
+
     static final boolean LOGD = false;
-    static final boolean CROSSFADE = false;
 
     static final int VIEW_MODE_NOINIT = 0;
     static final int VIEW_MODE_CONTENT = 1;
     static final int VIEW_MODE_ERROR = 2;
     static final int VIEW_MODE_DEFAULT = 3;
 
-    static final int FADE_DURATION = 1000;
-
     // When we're inflating the initialLayout for a AppWidget, we only allow
     // views that are allowed in RemoteViews.
     static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
@@ -85,9 +80,6 @@
     View mView;
     int mViewMode = VIEW_MODE_NOINIT;
     int mLayoutId = -1;
-    long mFadeStartTime = -1;
-    Bitmap mOld;
-    Paint mOldPaint = new Paint();
     private OnClickHandler mOnClickHandler;
 
     private Executor mAsyncExecutor;
@@ -212,9 +204,12 @@
 
     @Override
     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
-        final ParcelableSparseArray jail = new ParcelableSparseArray();
+        final SparseArray<Parcelable> jail = new SparseArray<>();
         super.dispatchSaveInstanceState(jail);
-        container.put(generateId(), jail);
+
+        Bundle bundle = new Bundle();
+        bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail);
+        container.put(generateId(), bundle);
     }
 
     private int generateId() {
@@ -226,12 +221,12 @@
     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
         final Parcelable parcelable = container.get(generateId());
 
-        ParcelableSparseArray jail = null;
-        if (parcelable != null && parcelable instanceof ParcelableSparseArray) {
-            jail = (ParcelableSparseArray) parcelable;
+        SparseArray<Parcelable> jail = null;
+        if (parcelable instanceof Bundle) {
+            jail = ((Bundle) parcelable).getSparseParcelableArray(KEY_JAILED_ARRAY);
         }
 
-        if (jail == null) jail = new ParcelableSparseArray();
+        if (jail == null) jail = new SparseArray<>();
 
         try  {
             super.dispatchRestoreInstanceState(jail);
@@ -383,31 +378,10 @@
      * @hide
      */
     protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) {
-        if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
-
         boolean recycled = false;
         View content = null;
         Exception exception = null;
 
-        // Capture the old view into a bitmap so we can do the crossfade.
-        if (CROSSFADE) {
-            if (mFadeStartTime < 0) {
-                if (mView != null) {
-                    final int width = mView.getWidth();
-                    final int height = mView.getHeight();
-                    try {
-                        mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-                    } catch (OutOfMemoryError e) {
-                        // we just won't do the fade
-                        mOld = null;
-                    }
-                    if (mOld != null) {
-                        //mView.drawIntoBitmap(mOld);
-                    }
-                }
-            }
-        }
-
         if (mLastExecutionSignal != null) {
             mLastExecutionSignal.cancel();
             mLastExecutionSignal = null;
@@ -484,16 +458,6 @@
             removeView(mView);
             mView = content;
         }
-
-        if (CROSSFADE) {
-            if (mFadeStartTime < 0) {
-                // if there is already an animation in progress, don't do anything --
-                // the new view will pop in on top of the old one during the cross fade,
-                // and that looks okay.
-                mFadeStartTime = SystemClock.uptimeMillis();
-                invalidate();
-            }
-        }
     }
 
     private void updateContentDescription(AppWidgetProviderInfo info) {
@@ -617,45 +581,6 @@
         }
     }
 
-    @Override
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        if (CROSSFADE) {
-            int alpha;
-            int l = child.getLeft();
-            int t = child.getTop();
-            if (mFadeStartTime > 0) {
-                alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
-                if (alpha > 255) {
-                    alpha = 255;
-                }
-                Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
-                        + " w=" + child.getWidth());
-                if (alpha != 255 && mOld != null) {
-                    mOldPaint.setAlpha(255-alpha);
-                    //canvas.drawBitmap(mOld, l, t, mOldPaint);
-                }
-            } else {
-                alpha = 255;
-            }
-            int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
-                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
-            boolean rv = super.drawChild(canvas, child, drawingTime);
-            canvas.restoreToCount(restoreTo);
-            if (alpha < 255) {
-                invalidate();
-            } else {
-                mFadeStartTime = -1;
-                if (mOld != null) {
-                    mOld.recycle();
-                    mOld = null;
-                }
-            }
-            return rv;
-        } else {
-            return super.drawChild(canvas, child, drawingTime);
-        }
-    }
-
     /**
      * Prepare the given view to be shown. This might include adjusting
      * {@link FrameLayout.LayoutParams} before inserting.
@@ -740,36 +665,4 @@
         super.onInitializeAccessibilityNodeInfoInternal(info);
         info.setClassName(AppWidgetHostView.class.getName());
     }
-
-    private static class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
-        public int describeContents() {
-            return 0;
-        }
-
-        public void writeToParcel(Parcel dest, int flags) {
-            final int count = size();
-            dest.writeInt(count);
-            for (int i = 0; i < count; i++) {
-                dest.writeInt(keyAt(i));
-                dest.writeParcelable(valueAt(i), 0);
-            }
-        }
-
-        public static final Parcelable.Creator<ParcelableSparseArray> CREATOR =
-                new Parcelable.Creator<ParcelableSparseArray>() {
-                    public ParcelableSparseArray createFromParcel(Parcel source) {
-                        final ParcelableSparseArray array = new ParcelableSparseArray();
-                        final ClassLoader loader = array.getClass().getClassLoader();
-                        final int count = source.readInt();
-                        for (int i = 0; i < count; i++) {
-                            array.put(source.readInt(), source.readParcelable(loader));
-                        }
-                        return array;
-                    }
-
-                    public ParcelableSparseArray[] newArray(int size) {
-                        return new ParcelableSparseArray[size];
-                    }
-                };
-    }
 }
diff --git a/android/arch/core/executor/AppToolkitTaskExecutor.java b/android/arch/core/executor/ArchTaskExecutor.java
similarity index 88%
rename from android/arch/core/executor/AppToolkitTaskExecutor.java
rename to android/arch/core/executor/ArchTaskExecutor.java
index 7337f74..2401a73 100644
--- a/android/arch/core/executor/AppToolkitTaskExecutor.java
+++ b/android/arch/core/executor/ArchTaskExecutor.java
@@ -29,8 +29,8 @@
  * @hide This API is not final.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class AppToolkitTaskExecutor extends TaskExecutor {
-    private static volatile AppToolkitTaskExecutor sInstance;
+public class ArchTaskExecutor extends TaskExecutor {
+    private static volatile ArchTaskExecutor sInstance;
 
     @NonNull
     private TaskExecutor mDelegate;
@@ -54,7 +54,7 @@
         }
     };
 
-    private AppToolkitTaskExecutor() {
+    private ArchTaskExecutor() {
         mDefaultTaskExecutor = new DefaultTaskExecutor();
         mDelegate = mDefaultTaskExecutor;
     }
@@ -62,15 +62,15 @@
     /**
      * Returns an instance of the task executor.
      *
-     * @return The singleton AppToolkitTaskExecutor.
+     * @return The singleton ArchTaskExecutor.
      */
-    public static AppToolkitTaskExecutor getInstance() {
+    public static ArchTaskExecutor getInstance() {
         if (sInstance != null) {
             return sInstance;
         }
-        synchronized (AppToolkitTaskExecutor.class) {
+        synchronized (ArchTaskExecutor.class) {
             if (sInstance == null) {
-                sInstance = new AppToolkitTaskExecutor();
+                sInstance = new ArchTaskExecutor();
             }
         }
         return sInstance;
diff --git a/android/arch/core/executor/JunitTaskExecutorRule.java b/android/arch/core/executor/JunitTaskExecutorRule.java
index cd4f8f5..c3366f3 100644
--- a/android/arch/core/executor/JunitTaskExecutorRule.java
+++ b/android/arch/core/executor/JunitTaskExecutorRule.java
@@ -46,11 +46,11 @@
     }
 
     private void beforeStart() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
     }
 
     private void afterFinished() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     public TaskExecutor getTaskExecutor() {
diff --git a/android/arch/core/executor/testing/CountingTaskExecutorRule.java b/android/arch/core/executor/testing/CountingTaskExecutorRule.java
index ad930aa..77133d5 100644
--- a/android/arch/core/executor/testing/CountingTaskExecutorRule.java
+++ b/android/arch/core/executor/testing/CountingTaskExecutorRule.java
@@ -16,7 +16,7 @@
 
 package android.arch.core.executor.testing;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.DefaultTaskExecutor;
 import android.os.SystemClock;
 
@@ -39,7 +39,7 @@
     @Override
     protected void starting(Description description) {
         super.starting(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
             @Override
             public void executeOnDiskIO(Runnable runnable) {
                 super.executeOnDiskIO(new CountingRunnable(runnable));
@@ -55,7 +55,7 @@
     @Override
     protected void finished(Description description) {
         super.finished(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     private void increment() {
diff --git a/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java b/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
index ad36b9b..a6a5b2e 100644
--- a/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
+++ b/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
@@ -19,7 +19,7 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -115,13 +115,13 @@
 
     private LatchRunnable runOnIO() {
         LatchRunnable latchRunnable = new LatchRunnable();
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
+        ArchTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
         return latchRunnable;
     }
 
     private LatchRunnable runOnMain() {
         LatchRunnable latchRunnable = new LatchRunnable();
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
+        ArchTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
         return latchRunnable;
     }
 
diff --git a/android/arch/core/executor/testing/InstantTaskExecutorRule.java b/android/arch/core/executor/testing/InstantTaskExecutorRule.java
index 07dcf1f..f88a3e3 100644
--- a/android/arch/core/executor/testing/InstantTaskExecutorRule.java
+++ b/android/arch/core/executor/testing/InstantTaskExecutorRule.java
@@ -16,7 +16,7 @@
 
 package android.arch.core.executor.testing;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 
 import org.junit.rules.TestWatcher;
@@ -32,7 +32,7 @@
     @Override
     protected void starting(Description description) {
         super.starting(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
             @Override
             public void executeOnDiskIO(Runnable runnable) {
                 runnable.run();
@@ -53,6 +53,6 @@
     @Override
     protected void finished(Description description) {
         super.finished(description);
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 }
diff --git a/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java b/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
index 4345fd1..0fdcbfb 100644
--- a/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
+++ b/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
@@ -18,7 +18,7 @@
 
 import static org.junit.Assert.assertTrue;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -46,7 +46,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(check);
+        ArchTaskExecutor.getInstance().executeOnMainThread(check);
         check.get(1, TimeUnit.SECONDS);
     }
 
@@ -60,7 +60,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(check);
+        ArchTaskExecutor.getInstance().executeOnDiskIO(check);
         check.get(1, TimeUnit.SECONDS);
     }
 }
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
new file mode 100644
index 0000000..f077dae
--- /dev/null
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -0,0 +1,237 @@
+/*
+ * 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.support.annotation.Nullable;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Reflection is expensive, so we cache information about methods
+ * for {@link ReflectiveGenericLifecycleObserver}, so it can call them,
+ * and for {@link Lifecycling} to determine which observer adapter to use.
+ */
+class ClassesInfoCache {
+
+    static ClassesInfoCache sInstance = new ClassesInfoCache();
+
+    private static final int CALL_TYPE_NO_ARG = 0;
+    private static final int CALL_TYPE_PROVIDER = 1;
+    private static final int CALL_TYPE_PROVIDER_WITH_EVENT = 2;
+
+    private final Map<Class, CallbackInfo> mCallbackMap = new HashMap<>();
+    private final Map<Class, Boolean> mHasLifecycleMethods = new HashMap<>();
+
+    boolean hasLifecycleMethods(Class klass) {
+        if (mHasLifecycleMethods.containsKey(klass)) {
+            return mHasLifecycleMethods.get(klass);
+        }
+
+        Method[] methods = klass.getDeclaredMethods();
+        for (Method method : methods) {
+            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
+            if (annotation != null) {
+                // Optimization for reflection, we know that this method is called
+                // when there is no generated adapter. But there are methods with @OnLifecycleEvent
+                // so we know that will use ReflectiveGenericLifecycleObserver,
+                // so we createInfo in advance.
+                // CreateInfo always initialize mHasLifecycleMethods for a class, so we don't do it
+                // here.
+                createInfo(klass, methods);
+                return true;
+            }
+        }
+        mHasLifecycleMethods.put(klass, false);
+        return false;
+    }
+
+    CallbackInfo getInfo(Class klass) {
+        CallbackInfo existing = mCallbackMap.get(klass);
+        if (existing != null) {
+            return existing;
+        }
+        existing = createInfo(klass, null);
+        return existing;
+    }
+
+    private void verifyAndPutHandler(Map<MethodReference, Lifecycle.Event> handlers,
+            MethodReference newHandler, Lifecycle.Event newEvent, Class klass) {
+        Lifecycle.Event event = handlers.get(newHandler);
+        if (event != null && newEvent != event) {
+            Method method = newHandler.mMethod;
+            throw new IllegalArgumentException(
+                    "Method " + method.getName() + " in " + klass.getName()
+                            + " already declared with different @OnLifecycleEvent value: previous"
+                            + " value " + event + ", new value " + newEvent);
+        }
+        if (event == null) {
+            handlers.put(newHandler, newEvent);
+        }
+    }
+
+    private CallbackInfo createInfo(Class klass, @Nullable Method[] declaredMethods) {
+        Class superclass = klass.getSuperclass();
+        Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>();
+        if (superclass != null) {
+            CallbackInfo superInfo = getInfo(superclass);
+            if (superInfo != null) {
+                handlerToEvent.putAll(superInfo.mHandlerToEvent);
+            }
+        }
+
+        Class[] interfaces = klass.getInterfaces();
+        for (Class intrfc : interfaces) {
+            for (Map.Entry<MethodReference, Lifecycle.Event> entry : getInfo(
+                    intrfc).mHandlerToEvent.entrySet()) {
+                verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
+            }
+        }
+
+        Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+        boolean hasLifecycleMethods = false;
+        for (Method method : methods) {
+            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
+            if (annotation == null) {
+                continue;
+            }
+            hasLifecycleMethods = true;
+            Class<?>[] params = method.getParameterTypes();
+            int callType = CALL_TYPE_NO_ARG;
+            if (params.length > 0) {
+                callType = CALL_TYPE_PROVIDER;
+                if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
+                    throw new IllegalArgumentException(
+                            "invalid parameter type. Must be one and instanceof LifecycleOwner");
+                }
+            }
+            Lifecycle.Event event = annotation.value();
+
+            if (params.length > 1) {
+                callType = CALL_TYPE_PROVIDER_WITH_EVENT;
+                if (!params[1].isAssignableFrom(Lifecycle.Event.class)) {
+                    throw new IllegalArgumentException(
+                            "invalid parameter type. second arg must be an event");
+                }
+                if (event != Lifecycle.Event.ON_ANY) {
+                    throw new IllegalArgumentException(
+                            "Second arg is supported only for ON_ANY value");
+                }
+            }
+            if (params.length > 2) {
+                throw new IllegalArgumentException("cannot have more than 2 params");
+            }
+            MethodReference methodReference = new MethodReference(callType, method);
+            verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
+        }
+        CallbackInfo info = new CallbackInfo(handlerToEvent);
+        mCallbackMap.put(klass, info);
+        mHasLifecycleMethods.put(klass, hasLifecycleMethods);
+        return info;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class CallbackInfo {
+        final Map<Lifecycle.Event, List<MethodReference>> mEventToHandlers;
+        final Map<MethodReference, Lifecycle.Event> mHandlerToEvent;
+
+        CallbackInfo(Map<MethodReference, Lifecycle.Event> handlerToEvent) {
+            mHandlerToEvent = handlerToEvent;
+            mEventToHandlers = new HashMap<>();
+            for (Map.Entry<MethodReference, Lifecycle.Event> entry : handlerToEvent.entrySet()) {
+                Lifecycle.Event event = entry.getValue();
+                List<MethodReference> methodReferences = mEventToHandlers.get(event);
+                if (methodReferences == null) {
+                    methodReferences = new ArrayList<>();
+                    mEventToHandlers.put(event, methodReferences);
+                }
+                methodReferences.add(entry.getKey());
+            }
+        }
+
+        @SuppressWarnings("ConstantConditions")
+        void invokeCallbacks(LifecycleOwner source, Lifecycle.Event event, Object target) {
+            invokeMethodsForEvent(mEventToHandlers.get(event), source, event, target);
+            invokeMethodsForEvent(mEventToHandlers.get(Lifecycle.Event.ON_ANY), source, event,
+                    target);
+        }
+
+        private static void invokeMethodsForEvent(List<MethodReference> handlers,
+                LifecycleOwner source, Lifecycle.Event event, Object mWrapped) {
+            if (handlers != null) {
+                for (int i = handlers.size() - 1; i >= 0; i--) {
+                    handlers.get(i).invokeCallback(source, event, mWrapped);
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class MethodReference {
+        final int mCallType;
+        final Method mMethod;
+
+        MethodReference(int callType, Method method) {
+            mCallType = callType;
+            mMethod = method;
+            mMethod.setAccessible(true);
+        }
+
+        void invokeCallback(LifecycleOwner source, Lifecycle.Event event, Object target) {
+            //noinspection TryWithIdenticalCatches
+            try {
+                switch (mCallType) {
+                    case CALL_TYPE_NO_ARG:
+                        mMethod.invoke(target);
+                        break;
+                    case CALL_TYPE_PROVIDER:
+                        mMethod.invoke(target, source);
+                        break;
+                    case CALL_TYPE_PROVIDER_WITH_EVENT:
+                        mMethod.invoke(target, source, event);
+                        break;
+                }
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException("Failed to call observer method", e.getCause());
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            MethodReference that = (MethodReference) o;
+            return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * mCallType + mMethod.getName().hashCode();
+        }
+    }
+}
diff --git a/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java b/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java
new file mode 100644
index 0000000..e8cbe7c
--- /dev/null
+++ b/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.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.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class CompositeGeneratedAdaptersObserver implements GenericLifecycleObserver {
+
+    private final GeneratedAdapter[] mGeneratedAdapters;
+
+    CompositeGeneratedAdaptersObserver(GeneratedAdapter[] generatedAdapters) {
+        mGeneratedAdapters = generatedAdapters;
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+        MethodCallsLogger logger = new MethodCallsLogger();
+        for (GeneratedAdapter mGenerated: mGeneratedAdapters) {
+            mGenerated.callMethods(source, event, false, logger);
+        }
+        for (GeneratedAdapter mGenerated: mGeneratedAdapters) {
+            mGenerated.callMethods(source, event, true, logger);
+        }
+    }
+}
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index fe18243..f135244 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,136 +1,9 @@
-/*
- * 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.
- */
-
+//ComputableLiveData interface for tests
 package android.arch.lifecycle;
-
-import android.arch.core.executor.AppToolkitTaskExecutor;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A LiveData class that can be invalidated & computed on demand.
- * <p>
- * This is an internal class for now, might be public if we see the necessity.
- *
- * @param <T> The type of the live data
- * @hide internal
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+import android.arch.lifecycle.LiveData;
 public abstract class ComputableLiveData<T> {
-
-    private final LiveData<T> mLiveData;
-
-    private AtomicBoolean mInvalid = new AtomicBoolean(true);
-    private AtomicBoolean mComputing = new AtomicBoolean(false);
-
-    /**
-     * Creates a computable live data which is computed when there are active observers.
-     * <p>
-     * It can also be invalidated via {@link #invalidate()} which will result in a call to
-     * {@link #compute()} if there are active observers (or when they start observing)
-     */
-    @SuppressWarnings("WeakerAccess")
-    public ComputableLiveData() {
-        mLiveData = new LiveData<T>() {
-            @Override
-            protected void onActive() {
-                // TODO if we make this class public, we should accept an executor
-                AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
-            }
-        };
-    }
-
-    /**
-     * Returns the LiveData managed by this class.
-     *
-     * @return A LiveData that is controlled by ComputableLiveData.
-     */
-    @SuppressWarnings("WeakerAccess")
-    @NonNull
-    public LiveData<T> getLiveData() {
-        return mLiveData;
-    }
-
-    @VisibleForTesting
-    final Runnable mRefreshRunnable = new Runnable() {
-        @WorkerThread
-        @Override
-        public void run() {
-            boolean computed;
-            do {
-                computed = false;
-                // compute can happen only in 1 thread but no reason to lock others.
-                if (mComputing.compareAndSet(false, true)) {
-                    // as long as it is invalid, keep computing.
-                    try {
-                        T value = null;
-                        while (mInvalid.compareAndSet(true, false)) {
-                            computed = true;
-                            value = compute();
-                        }
-                        if (computed) {
-                            mLiveData.postValue(value);
-                        }
-                    } finally {
-                        // release compute lock
-                        mComputing.set(false);
-                    }
-                }
-                // check invalid after releasing compute lock to avoid the following scenario.
-                // Thread A runs compute()
-                // Thread A checks invalid, it is false
-                // Main thread sets invalid to true
-                // Thread B runs, fails to acquire compute lock and skips
-                // Thread A releases compute lock
-                // We've left invalid in set state. The check below recovers.
-            } while (computed && mInvalid.get());
-        }
-    };
-
-    // invalidation check always happens on the main thread
-    @VisibleForTesting
-    final Runnable mInvalidationRunnable = new Runnable() {
-        @MainThread
-        @Override
-        public void run() {
-            boolean isActive = mLiveData.hasActiveObservers();
-            if (mInvalid.compareAndSet(false, true)) {
-                if (isActive) {
-                    // TODO if we make this class public, we should accept an executor.
-                    AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
-                }
-            }
-        }
-    };
-
-    /**
-     * Invalidates the LiveData.
-     * <p>
-     * When there are active observers, this will trigger a call to {@link #compute()}.
-     */
-    public void invalidate() {
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @WorkerThread
-    protected abstract T compute();
+    public ComputableLiveData(){}
+    abstract protected T compute();
+    public LiveData<T> getLiveData() {return null;}
+    public void invalidate() {}
 }
diff --git a/android/arch/lifecycle/ComputableLiveDataTest.java b/android/arch/lifecycle/ComputableLiveDataTest.java
index 0a3fbed..eb89d8d 100644
--- a/android/arch/lifecycle/ComputableLiveDataTest.java
+++ b/android/arch/lifecycle/ComputableLiveDataTest.java
@@ -27,7 +27,7 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.arch.core.executor.TaskExecutorWithFakeMainThread;
 import android.arch.lifecycle.util.InstantTaskExecutor;
@@ -58,12 +58,12 @@
     @Before
     public void swapExecutorDelegate() {
         mTaskExecutor = spy(new InstantTaskExecutor());
-        AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
     }
 
     @After
     public void removeExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     @Test
@@ -76,7 +76,7 @@
     @Test
     public void noConcurrentCompute() throws InterruptedException {
         TaskExecutorWithFakeMainThread executor = new TaskExecutorWithFakeMainThread(2);
-        AppToolkitTaskExecutor.getInstance().setDelegate(executor);
+        ArchTaskExecutor.getInstance().setDelegate(executor);
         try {
             // # of compute calls
             final Semaphore computeCounter = new Semaphore(0);
@@ -121,7 +121,7 @@
             // assert no other results arrive
             verify(observer, never()).onChanged(anyInt());
         } finally {
-            AppToolkitTaskExecutor.getInstance().setDelegate(null);
+            ArchTaskExecutor.getInstance().setDelegate(null);
         }
     }
 
diff --git a/android/arch/lifecycle/DefaultLifecycleObserver.java b/android/arch/lifecycle/DefaultLifecycleObserver.java
new file mode 100644
index 0000000..b6f468c
--- /dev/null
+++ b/android/arch/lifecycle/DefaultLifecycleObserver.java
@@ -0,0 +1,100 @@
+/*
+ * 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.support.annotation.NonNull;
+
+/**
+ * Callback interface for listening to {@link LifecycleOwner} state changes.
+ * <p>
+ * If you use Java 8 language, <b>always</b> prefer it over annotations.
+ */
+@SuppressWarnings("unused")
+public interface DefaultLifecycleObserver extends FullLifecycleObserver {
+
+    /**
+     * Notifies that {@code ON_CREATE} event occurred.
+     * <p>
+     * This method will be called after the {@link LifecycleOwner}'s {@code onCreate}
+     * method returns.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onCreate(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_START} event occurred.
+     * <p>
+     * This method will be called after the {@link LifecycleOwner}'s {@code onStart} method returns.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onStart(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_RESUME} event occurred.
+     * <p>
+     * This method will be called after the {@link LifecycleOwner}'s {@code onResume}
+     * method returns.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onResume(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_PAUSE} event occurred.
+     * <p>
+     * This method will be called before the {@link LifecycleOwner}'s {@code onPause} method
+     * is called.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onPause(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_STOP} event occurred.
+     * <p>
+     * This method will be called before the {@link LifecycleOwner}'s {@code onStop} method
+     * is called.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onStop(@NonNull LifecycleOwner owner) {
+    }
+
+    /**
+     * Notifies that {@code ON_DESTROY} event occurred.
+     * <p>
+     * This method will be called before the {@link LifecycleOwner}'s {@code onStop} method
+     * is called.
+     *
+     * @param owner the component, whose state was changed
+     */
+    @Override
+    default void onDestroy(@NonNull LifecycleOwner owner) {
+    }
+}
+
diff --git a/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java b/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
index 3397f5f..f48f788 100644
--- a/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
+++ b/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
@@ -37,6 +37,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 
@@ -58,20 +59,20 @@
         final ArrayList<Event> collectedEvents = new ArrayList<>();
         LifecycleObserver collectingObserver = new LifecycleObserver() {
             @OnLifecycleEvent(Event.ON_ANY)
-            void onAny(LifecycleOwner owner, Event event) {
+            void onAny(@SuppressWarnings("unused") LifecycleOwner owner, Event event) {
                 collectedEvents.add(event);
             }
         };
         final FragmentActivity activity = activityTestRule.getActivity();
         activityTestRule.runOnUiThread(() -> {
             FragmentManager fm = activity.getSupportFragmentManager();
-            LifecycleFragment fragment = new LifecycleFragment();
+            Fragment fragment = new Fragment();
             fm.beginTransaction().add(R.id.fragment_container, fragment, "tag").addToBackStack(null)
                     .commit();
             fm.executePendingTransactions();
 
             fragment.getLifecycle().addObserver(collectingObserver);
-            LifecycleFragment fragment2 = new LifecycleFragment();
+            Fragment fragment2 = new Fragment();
             fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
                     .commit();
             fm.executePendingTransactions();
@@ -82,12 +83,13 @@
         EmptyActivity newActivity = recreateActivity(activityTestRule.getActivity(),
                 activityTestRule);
 
+        //noinspection ArraysAsListWithZeroOrOneArgument
         assertThat(collectedEvents, is(asList(ON_DESTROY)));
         collectedEvents.clear();
         EmptyActivity lastActivity = recreateActivity(newActivity, activityTestRule);
         activityTestRule.runOnUiThread(() -> {
             FragmentManager fm = lastActivity.getSupportFragmentManager();
-            LifecycleFragment fragment = (LifecycleFragment) fm.findFragmentByTag("tag");
+            Fragment fragment = fm.findFragmentByTag("tag");
             fragment.getLifecycle().addObserver(collectingObserver);
             assertThat(collectedEvents, iterableWithSize(0));
             fm.popBackStackImmediate();
diff --git a/android/arch/lifecycle/FragmentOperationsLifecycleTest.java b/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
index be062cb..3e61277 100644
--- a/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
+++ b/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
@@ -34,6 +34,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 
 import org.junit.Rule;
@@ -55,7 +56,7 @@
     @UiThreadTest
     public void addRemoveFragment() {
         EmptyActivity activity = mActivityTestRule.getActivity();
-        LifecycleFragment fragment = new LifecycleFragment();
+        Fragment fragment = new Fragment();
         FragmentManager fm = activity.getSupportFragmentManager();
         fm.beginTransaction().add(fragment, "tag").commitNow();
         CollectingObserver observer = observeAndCollectIn(fragment);
@@ -70,7 +71,7 @@
     @UiThreadTest
     public void fragmentInBackstack() {
         EmptyActivity activity = mActivityTestRule.getActivity();
-        LifecycleFragment fragment1 = new LifecycleFragment();
+        Fragment fragment1 = new Fragment();
         FragmentManager fm = activity.getSupportFragmentManager();
         fm.beginTransaction().add(R.id.fragment_container, fragment1, "tag").addToBackStack(null)
                 .commit();
@@ -78,7 +79,7 @@
         CollectingObserver observer1 = observeAndCollectIn(fragment1);
         assertThat(observer1.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
 
-        LifecycleFragment fragment2 = new LifecycleFragment();
+        Fragment fragment2 = new Fragment();
         fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
                 .commit();
         fm.executePendingTransactions();
@@ -95,7 +96,7 @@
         assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
     }
 
-    private static CollectingObserver observeAndCollectIn(LifecycleFragment fragment) {
+    private static CollectingObserver observeAndCollectIn(Fragment fragment) {
         CollectingObserver observer = new CollectingObserver();
         fragment.getLifecycle().addObserver(observer);
         return observer;
diff --git a/android/arch/lifecycle/FullLifecycleObserver.java b/android/arch/lifecycle/FullLifecycleObserver.java
new file mode 100644
index 0000000..f179274
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserver.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+interface FullLifecycleObserver extends LifecycleObserver {
+
+    void onCreate(LifecycleOwner owner);
+
+    void onStart(LifecycleOwner owner);
+
+    void onResume(LifecycleOwner owner);
+
+    void onPause(LifecycleOwner owner);
+
+    void onStop(LifecycleOwner owner);
+
+    void onDestroy(LifecycleOwner owner);
+}
diff --git a/android/arch/lifecycle/FullLifecycleObserverAdapter.java b/android/arch/lifecycle/FullLifecycleObserverAdapter.java
new file mode 100644
index 0000000..0a91a66
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserverAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+class FullLifecycleObserverAdapter implements GenericLifecycleObserver {
+
+    private final FullLifecycleObserver mObserver;
+
+    FullLifecycleObserverAdapter(FullLifecycleObserver observer) {
+        mObserver = observer;
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+        switch (event) {
+            case ON_CREATE:
+                mObserver.onCreate(source);
+                break;
+            case ON_START:
+                mObserver.onStart(source);
+                break;
+            case ON_RESUME:
+                mObserver.onResume(source);
+                break;
+            case ON_PAUSE:
+                mObserver.onPause(source);
+                break;
+            case ON_STOP:
+                mObserver.onStop(source);
+                break;
+            case ON_DESTROY:
+                mObserver.onDestroy(source);
+                break;
+            case ON_ANY:
+                throw new IllegalArgumentException("ON_ANY must not been send by anybody");
+        }
+    }
+}
diff --git a/android/arch/lifecycle/FullLifecycleObserverTest.java b/android/arch/lifecycle/FullLifecycleObserverTest.java
new file mode 100644
index 0000000..def6755
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserverTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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_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.INITIALIZED;
+import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public class FullLifecycleObserverTest {
+    private LifecycleOwner mOwner;
+    private Lifecycle mLifecycle;
+
+    @Before
+    public void initMocks() {
+        mOwner = mock(LifecycleOwner.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mOwner.getLifecycle()).thenReturn(mLifecycle);
+    }
+
+    @Test
+    public void eachEvent() {
+        FullLifecycleObserver obj = mock(FullLifecycleObserver.class);
+        FullLifecycleObserverAdapter observer = new FullLifecycleObserverAdapter(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+
+        observer.onStateChanged(mOwner, ON_CREATE);
+        InOrder inOrder = Mockito.inOrder(obj);
+        inOrder.verify(obj).onCreate(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_START);
+        inOrder.verify(obj).onStart(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(RESUMED);
+        observer.onStateChanged(mOwner, ON_RESUME);
+        inOrder.verify(obj).onResume(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_PAUSE);
+        inOrder.verify(obj).onPause(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+        observer.onStateChanged(mOwner, ON_STOP);
+        inOrder.verify(obj).onStop(mOwner);
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(INITIALIZED);
+        observer.onStateChanged(mOwner, ON_DESTROY);
+        inOrder.verify(obj).onDestroy(mOwner);
+        reset(obj);
+    }
+}
diff --git a/android/arch/lifecycle/GeneratedAdapter.java b/android/arch/lifecycle/GeneratedAdapter.java
new file mode 100644
index 0000000..a8862da
--- /dev/null
+++ b/android/arch/lifecycle/GeneratedAdapter.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 android.arch.lifecycle;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface GeneratedAdapter {
+
+    /**
+     * Called when a state transition event happens.
+     *
+     * @param source The source of the event
+     * @param event The event
+     * @param onAny approveCall onAny handlers
+     * @param logger if passed, used to track called methods and prevent calling the same method
+     *              twice
+     */
+    void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger);
+}
diff --git a/android/arch/lifecycle/GeneratedAdaptersTest.java b/android/arch/lifecycle/GeneratedAdaptersTest.java
new file mode 100644
index 0000000..2abb511
--- /dev/null
+++ b/android/arch/lifecycle/GeneratedAdaptersTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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_ANY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class GeneratedAdaptersTest {
+
+    private LifecycleOwner mOwner;
+    @SuppressWarnings("FieldCanBeLocal")
+    private Lifecycle mLifecycle;
+
+    @Before
+    public void initMocks() {
+        mOwner = mock(LifecycleOwner.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mOwner.getLifecycle()).thenReturn(mLifecycle);
+    }
+
+    static class SimpleObserver implements LifecycleObserver {
+        List<String> mLog;
+
+        SimpleObserver(List<String> log) {
+            mLog = log;
+        }
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+        void onCreate() {
+            mLog.add("onCreate");
+        }
+    }
+
+    @Test
+    public void testSimpleSingleGeneratedAdapter() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new SimpleObserver(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_CREATE);
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+        assertThat(actual, is(singletonList("onCreate")));
+    }
+
+    static class TestObserver implements LifecycleObserver {
+        List<String> mLog;
+
+        TestObserver(List<String> log) {
+            mLog = log;
+        }
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+        void onCreate() {
+            mLog.add("onCreate");
+        }
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny() {
+            mLog.add("onAny");
+        }
+    }
+
+    @Test
+    public void testOnAny() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new TestObserver(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_CREATE);
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+        assertThat(actual, is(asList("onCreate", "onAny")));
+    }
+
+    interface OnPauses extends LifecycleObserver {
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        void onPause();
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        void onPause(LifecycleOwner owner);
+    }
+
+    interface OnPauseResume extends LifecycleObserver {
+        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        void onPause();
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+        void onResume();
+    }
+
+    class Impl1 implements OnPauses, OnPauseResume {
+
+        List<String> mLog;
+
+        Impl1(List<String> log) {
+            mLog = log;
+        }
+
+        @Override
+        public void onPause() {
+            mLog.add("onPause_0");
+        }
+
+        @Override
+        public void onResume() {
+            mLog.add("onResume");
+        }
+
+        @Override
+        public void onPause(LifecycleOwner owner) {
+            mLog.add("onPause_1");
+        }
+    }
+
+    @Test
+    public void testClashingInterfaces() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new Impl1(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_PAUSE);
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+        assertThat(actual, is(asList("onPause_0", "onPause_1")));
+        actual.clear();
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_RESUME);
+        assertThat(actual, is(singletonList("onResume")));
+    }
+
+    class Base implements LifecycleObserver {
+
+        List<String> mLog;
+
+        Base(List<String> log) {
+            mLog = log;
+        }
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny() {
+            mLog.add("onAny_0");
+        }
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny(LifecycleOwner owner) {
+            mLog.add("onAny_1");
+        }
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+        void onResume() {
+            mLog.add("onResume");
+        }
+    }
+
+    interface OnAny extends LifecycleObserver {
+        @OnLifecycleEvent(ON_ANY)
+        void onAny();
+
+        @OnLifecycleEvent(ON_ANY)
+        void onAny(LifecycleOwner owner, Lifecycle.Event event);
+    }
+
+    class Derived extends Base implements OnAny {
+        Derived(List<String> log) {
+            super(log);
+        }
+
+        @Override
+        public void onAny() {
+            super.onAny();
+        }
+
+        @Override
+        public void onAny(LifecycleOwner owner, Lifecycle.Event event) {
+            mLog.add("onAny_2");
+            assertThat(event, is(ON_RESUME));
+        }
+    }
+
+    @Test
+    public void testClashingClassAndInterface() {
+        List<String>  actual = new ArrayList<>();
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new Derived(actual));
+        callback.onStateChanged(mOwner, Lifecycle.Event.ON_RESUME);
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+        assertThat(actual, is(asList("onResume", "onAny_0", "onAny_1", "onAny_2")));
+    }
+
+}
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index fcbd50a..02db5ff 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -34,7 +34,21 @@
  * before {@link android.app.Activity#onStop onStop} is called.
  * This gives you certain guarantees on which state the owner is in.
  * <p>
- * Lifecycle events are observed using annotations.
+ * If you use <b>Java 8 Language</b>, then observe events with {@link DefaultLifecycleObserver}.
+ * To include it you should add {@code "android.arch.lifecycle:common-java8:<version>"} to your
+ * build.gradle file.
+ * <pre>
+ * class TestObserver implements DefaultLifecycleObserver {
+ *     {@literal @}Override
+ *     public void onCreate(LifecycleOwner owner) {
+ *         // your code
+ *     }
+ * }
+ * </pre>
+ * If you use <b>Java 7 Language</b>, Lifecycle events are observed using annotations.
+ * Once Java 8 Language becomes mainstream on Android, annotations will be deprecated, so between
+ * {@link DefaultLifecycleObserver} and annotations,
+ * you must always prefer {@code DefaultLifecycleObserver}.
  * <pre>
  * class TestObserver implements LifecycleObserver {
  *   {@literal @}OnLifecycleEvent(ON_STOP)
@@ -42,16 +56,6 @@
  * }
  * </pre>
  * <p>
- * Multiple methods can observe the same event.
- * <pre>
- * class TestObserver implements LifecycleObserver {
- *   {@literal @}OnLifecycleEvent(ON_STOP)
- *   void onStoppedFirst() {}
- *   {@literal @}OnLifecycleEvent(ON_STOP)
- *   void onStoppedSecond() {}
- * }
- * </pre>
- * <p>
  * Observer methods can receive zero or one argument.
  * If used, the first argument must be of type {@link LifecycleOwner}.
  * Methods annotated with {@link Event#ON_ANY} can receive the second argument, which must be
diff --git a/android/arch/lifecycle/Lifecycling.java b/android/arch/lifecycle/Lifecycling.java
index 3a5c0b9..7d6b37f 100644
--- a/android/arch/lifecycle/Lifecycling.java
+++ b/android/arch/lifecycle/Lifecycling.java
@@ -22,52 +22,61 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
  * Internal class to handle lifecycle conversion etc.
+ *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class Lifecycling {
-    private static Constructor<? extends GenericLifecycleObserver> sREFLECTIVE;
+public class Lifecycling {
 
-    static {
-        try {
-            sREFLECTIVE = ReflectiveGenericLifecycleObserver.class
-                    .getDeclaredConstructor(Object.class);
-        } catch (NoSuchMethodException ignored) {
+    private static final int REFLECTIVE_CALLBACK = 1;
+    private static final int GENERATED_CALLBACK = 2;
 
-        }
-    }
-
-    private static Map<Class, Constructor<? extends GenericLifecycleObserver>> sCallbackCache =
+    private static Map<Class, Integer> sCallbackCache = new HashMap<>();
+    private static Map<Class, List<Constructor<? extends GeneratedAdapter>>> sClassToAdapters =
             new HashMap<>();
 
     @NonNull
     static GenericLifecycleObserver getCallback(Object object) {
+        if (object instanceof FullLifecycleObserver) {
+            return new FullLifecycleObserverAdapter((FullLifecycleObserver) object);
+        }
+
         if (object instanceof GenericLifecycleObserver) {
             return (GenericLifecycleObserver) object;
         }
+
+        final Class<?> klass = object.getClass();
+        int type = getObserverConstructorType(klass);
+        if (type == GENERATED_CALLBACK) {
+            List<Constructor<? extends GeneratedAdapter>> constructors =
+                    sClassToAdapters.get(klass);
+            if (constructors.size() == 1) {
+                GeneratedAdapter generatedAdapter = createGeneratedAdapter(
+                        constructors.get(0), object);
+                return new SingleGeneratedAdapterObserver(generatedAdapter);
+            }
+            GeneratedAdapter[] adapters = new GeneratedAdapter[constructors.size()];
+            for (int i = 0; i < constructors.size(); i++) {
+                adapters[i] = createGeneratedAdapter(constructors.get(i), object);
+            }
+            return new CompositeGeneratedAdaptersObserver(adapters);
+        }
+        return new ReflectiveGenericLifecycleObserver(object);
+    }
+
+    private static GeneratedAdapter createGeneratedAdapter(
+            Constructor<? extends GeneratedAdapter> constructor, Object object) {
         //noinspection TryWithIdenticalCatches
         try {
-            final Class<?> klass = object.getClass();
-            Constructor<? extends GenericLifecycleObserver> cachedConstructor = sCallbackCache.get(
-                    klass);
-            if (cachedConstructor != null) {
-                return cachedConstructor.newInstance(object);
-            }
-            cachedConstructor = getGeneratedAdapterConstructor(klass);
-            if (cachedConstructor != null) {
-                if (!cachedConstructor.isAccessible()) {
-                    cachedConstructor.setAccessible(true);
-                }
-            } else {
-                cachedConstructor = sREFLECTIVE;
-            }
-            sCallbackCache.put(klass, cachedConstructor);
-            return cachedConstructor.newInstance(object);
+            return constructor.newInstance(object);
         } catch (IllegalAccessException e) {
             throw new RuntimeException(e);
         } catch (InstantiationException e) {
@@ -78,37 +87,95 @@
     }
 
     @Nullable
-    private static Constructor<? extends GenericLifecycleObserver> getGeneratedAdapterConstructor(
-            Class<?> klass) {
-        Package aPackage = klass.getPackage();
-        final String fullPackage = aPackage != null ? aPackage.getName() : "";
-
-        String name = klass.getCanonicalName();
-        // anonymous class bug:35073837
-        if (name == null) {
-            return null;
-        }
-        final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
-                name.substring(fullPackage.length() + 1));
+    private static Constructor<? extends GeneratedAdapter> generatedConstructor(Class<?> klass) {
         try {
-            @SuppressWarnings("unchecked")
-            final Class<? extends GenericLifecycleObserver> aClass =
-                    (Class<? extends GenericLifecycleObserver>) Class.forName(
+            Package aPackage = klass.getPackage();
+            String name = klass.getCanonicalName();
+            final String fullPackage = aPackage != null ? aPackage.getName() : "";
+            final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
+                    name.substring(fullPackage.length() + 1));
+
+            @SuppressWarnings("unchecked") final Class<? extends GeneratedAdapter> aClass =
+                    (Class<? extends GeneratedAdapter>) Class.forName(
                             fullPackage.isEmpty() ? adapterName : fullPackage + "." + adapterName);
-            return aClass.getDeclaredConstructor(klass);
-        } catch (ClassNotFoundException e) {
-            final Class<?> superclass = klass.getSuperclass();
-            if (superclass != null) {
-                return getGeneratedAdapterConstructor(superclass);
+            Constructor<? extends GeneratedAdapter> constructor =
+                    aClass.getDeclaredConstructor(klass);
+            if (!constructor.isAccessible()) {
+                constructor.setAccessible(true);
             }
+            return constructor;
+        } catch (ClassNotFoundException e) {
+            return null;
         } catch (NoSuchMethodException e) {
             // this should not happen
             throw new RuntimeException(e);
         }
-        return null;
     }
 
-    static String getAdapterName(String className) {
+    private static int getObserverConstructorType(Class<?> klass) {
+        if (sCallbackCache.containsKey(klass)) {
+            return sCallbackCache.get(klass);
+        }
+        int type = resolveObserverCallbackType(klass);
+        sCallbackCache.put(klass, type);
+        return type;
+    }
+
+    private static int resolveObserverCallbackType(Class<?> klass) {
+        // anonymous class bug:35073837
+        if (klass.getCanonicalName() == null) {
+            return REFLECTIVE_CALLBACK;
+        }
+
+        Constructor<? extends GeneratedAdapter> constructor = generatedConstructor(klass);
+        if (constructor != null) {
+            sClassToAdapters.put(klass, Collections
+                    .<Constructor<? extends GeneratedAdapter>>singletonList(constructor));
+            return GENERATED_CALLBACK;
+        }
+
+        boolean hasLifecycleMethods = ClassesInfoCache.sInstance.hasLifecycleMethods(klass);
+        if (hasLifecycleMethods) {
+            return REFLECTIVE_CALLBACK;
+        }
+
+        Class<?> superclass = klass.getSuperclass();
+        List<Constructor<? extends GeneratedAdapter>> adapterConstructors = null;
+        if (isLifecycleParent(superclass)) {
+            if (getObserverConstructorType(superclass) == REFLECTIVE_CALLBACK) {
+                return REFLECTIVE_CALLBACK;
+            }
+            adapterConstructors = new ArrayList<>(sClassToAdapters.get(superclass));
+        }
+
+        for (Class<?> intrface : klass.getInterfaces()) {
+            if (!isLifecycleParent(intrface)) {
+                continue;
+            }
+            if (getObserverConstructorType(intrface) == REFLECTIVE_CALLBACK) {
+                return REFLECTIVE_CALLBACK;
+            }
+            if (adapterConstructors == null) {
+                adapterConstructors = new ArrayList<>();
+            }
+            adapterConstructors.addAll(sClassToAdapters.get(intrface));
+        }
+        if (adapterConstructors != null) {
+            sClassToAdapters.put(klass, adapterConstructors);
+            return GENERATED_CALLBACK;
+        }
+
+        return REFLECTIVE_CALLBACK;
+    }
+
+    private static boolean isLifecycleParent(Class<?> klass) {
+        return klass != null && LifecycleObserver.class.isAssignableFrom(klass);
+    }
+
+    /**
+     * Create a name for an adapter class.
+     */
+    public static String getAdapterName(String className) {
         return className.replace(".", "_") + "_LifecycleAdapter";
     }
 }
diff --git a/android/arch/lifecycle/LifecyclingTest.java b/android/arch/lifecycle/LifecyclingTest.java
new file mode 100644
index 0000000..70ce84c
--- /dev/null
+++ b/android/arch/lifecycle/LifecyclingTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.lifecycle.observers.DerivedSequence1;
+import android.arch.lifecycle.observers.DerivedSequence2;
+import android.arch.lifecycle.observers.DerivedWithNewMethods;
+import android.arch.lifecycle.observers.DerivedWithNoNewMethods;
+import android.arch.lifecycle.observers.DerivedWithOverridenMethodsWithLfAnnotation;
+import android.arch.lifecycle.observers.InterfaceImpl1;
+import android.arch.lifecycle.observers.InterfaceImpl2;
+import android.arch.lifecycle.observers.InterfaceImpl3;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LifecyclingTest {
+
+    @Test
+    public void testDerivedWithNewLfMethodsNoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new DerivedWithNewMethods());
+        assertThat(callback, instanceOf(ReflectiveGenericLifecycleObserver.class));
+    }
+
+    @Test
+    public void testDerivedWithNoNewLfMethodsNoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new DerivedWithNoNewMethods());
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+    }
+
+    @Test
+    public void testDerivedWithOverridenMethodsNoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(
+                new DerivedWithOverridenMethodsWithLfAnnotation());
+        // that is not effective but...
+        assertThat(callback, instanceOf(ReflectiveGenericLifecycleObserver.class));
+    }
+
+    @Test
+    public void testInterfaceImpl1NoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl1());
+        assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+    }
+
+    @Test
+    public void testInterfaceImpl2NoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl2());
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+    }
+
+    @Test
+    public void testInterfaceImpl3NoGeneratedAdapter() {
+        GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl3());
+        assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+    }
+
+    @Test
+    public void testDerivedSequence() {
+        GenericLifecycleObserver callback2 = Lifecycling.getCallback(new DerivedSequence2());
+        assertThat(callback2, instanceOf(ReflectiveGenericLifecycleObserver.class));
+        GenericLifecycleObserver callback1 = Lifecycling.getCallback(new DerivedSequence1());
+        assertThat(callback1, instanceOf(SingleGeneratedAdapterObserver.class));
+    }
+}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 99d859c..3aea6ac 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,411 +1,4 @@
-/*
- * 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.
- */
-
+//LiveData interface for tests
 package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-
-import android.arch.core.executor.AppToolkitTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * LiveData is a data holder class that can be observed within a given lifecycle.
- * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
- * this observer will be notified about modifications of the wrapped data only if the paired
- * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
- * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
- * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
- * about modifications. For those observers, you should manually call
- * {@link #removeObserver(Observer)}.
- *
- * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
- * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
- * activities and fragments where they can safely observe LiveData and not worry about leaks:
- * they will be instantly unsubscribed when they are destroyed.
- *
- * <p>
- * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
- * to get notified when number of active {@link Observer}s change between 0 and 1.
- * This allows LiveData to release any heavy resources when it does not have any Observers that
- * are actively observing.
- * <p>
- * This class is designed to hold individual data fields of {@link ViewModel},
- * but can also be used for sharing data between different modules in your application
- * in a decoupled fashion.
- *
- * @param <T> The type of data hold by this instance
- * @see ViewModel
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
-// thread.
-public abstract class LiveData<T> {
-    private final Object mDataLock = new Object();
-    static final int START_VERSION = -1;
-    private static final Object NOT_SET = new Object();
-
-    private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
-
-        private LifecycleRegistry mRegistry = init();
-
-        private LifecycleRegistry init() {
-            LifecycleRegistry registry = new LifecycleRegistry(this);
-            registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
-            registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-            registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
-            return registry;
-        }
-
-        @Override
-        public Lifecycle getLifecycle() {
-            return mRegistry;
-        }
-    };
-
-    private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
-            new SafeIterableMap<>();
-
-    // how many observers are in active state
-    private int mActiveCount = 0;
-    private volatile Object mData = NOT_SET;
-    // when setData is called, we set the pending data and actual data swap happens on the main
-    // thread
-    private volatile Object mPendingData = NOT_SET;
-    private int mVersion = START_VERSION;
-
-    private boolean mDispatchingValue;
-    @SuppressWarnings("FieldCanBeLocal")
-    private boolean mDispatchInvalidated;
-    private final Runnable mPostValueRunnable = new Runnable() {
-        @Override
-        public void run() {
-            Object newValue;
-            synchronized (mDataLock) {
-                newValue = mPendingData;
-                mPendingData = NOT_SET;
-            }
-            //noinspection unchecked
-            setValue((T) newValue);
-        }
-    };
-
-    private void considerNotify(LifecycleBoundObserver observer) {
-        if (!observer.active) {
-            return;
-        }
-        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
-        //
-        // we still first check observer.active to keep it as the entrance for events. So even if
-        // the observer moved to an active state, if we've not received that event, we better not
-        // notify for a more predictable notification order.
-        if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
-            return;
-        }
-        if (observer.lastVersion >= mVersion) {
-            return;
-        }
-        observer.lastVersion = mVersion;
-        //noinspection unchecked
-        observer.observer.onChanged((T) mData);
-    }
-
-    private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
-        if (mDispatchingValue) {
-            mDispatchInvalidated = true;
-            return;
-        }
-        mDispatchingValue = true;
-        do {
-            mDispatchInvalidated = false;
-            if (initiator != null) {
-                considerNotify(initiator);
-                initiator = null;
-            } else {
-                for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
-                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
-                    considerNotify(iterator.next().getValue());
-                    if (mDispatchInvalidated) {
-                        break;
-                    }
-                }
-            }
-        } while (mDispatchInvalidated);
-        mDispatchingValue = false;
-    }
-
-    /**
-     * Adds the given observer to the observers list within the lifespan of the given
-     * owner. The events are dispatched on the main thread. If LiveData already has data
-     * set, it will be delivered to the observer.
-     * <p>
-     * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
-     * or {@link Lifecycle.State#RESUMED} state (active).
-     * <p>
-     * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
-     * automatically be removed.
-     * <p>
-     * When data changes while the {@code owner} is not active, it will not receive any updates.
-     * If it becomes active again, it will receive the last available data automatically.
-     * <p>
-     * LiveData keeps a strong reference to the observer and the owner as long as the
-     * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
-     * the observer &amp; the owner.
-     * <p>
-     * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
-     * ignores the call.
-     * <p>
-     * If the given owner, observer tuple is already in the list, the call is ignored.
-     * If the observer is already in the list with another owner, LiveData throws an
-     * {@link IllegalArgumentException}.
-     *
-     * @param owner    The LifecycleOwner which controls the observer
-     * @param observer The observer that will receive the events
-     */
-    @MainThread
-    public void observe(LifecycleOwner owner, Observer<T> observer) {
-        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
-            // ignore
-            return;
-        }
-        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
-        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
-        if (existing != null && existing.owner != wrapper.owner) {
-            throw new IllegalArgumentException("Cannot add the same observer"
-                    + " with different lifecycles");
-        }
-        if (existing != null) {
-            return;
-        }
-        owner.getLifecycle().addObserver(wrapper);
-        wrapper.activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
-    }
-
-    /**
-     * Adds the given observer to the observers list. This call is similar to
-     * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
-     * is always active. This means that the given observer will receive all events and will never
-     * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
-     * observing this LiveData.
-     * While LiveData has one of such observers, it will be considered
-     * as active.
-     * <p>
-     * If the observer was already added with an owner to this LiveData, LiveData throws an
-     * {@link IllegalArgumentException}.
-     *
-     * @param observer The observer that will receive the events
-     */
-    @MainThread
-    public void observeForever(Observer<T> observer) {
-        observe(ALWAYS_ON, observer);
-    }
-
-    /**
-     * Removes the given observer from the observers list.
-     *
-     * @param observer The Observer to receive events.
-     */
-    @MainThread
-    public void removeObserver(final Observer<T> observer) {
-        assertMainThread("removeObserver");
-        LifecycleBoundObserver removed = mObservers.remove(observer);
-        if (removed == null) {
-            return;
-        }
-        removed.owner.getLifecycle().removeObserver(removed);
-        removed.activeStateChanged(false);
-    }
-
-    /**
-     * Removes all observers that are tied to the given {@link LifecycleOwner}.
-     *
-     * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
-     */
-    @MainThread
-    public void removeObservers(final LifecycleOwner owner) {
-        assertMainThread("removeObservers");
-        for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
-            if (entry.getValue().owner == owner) {
-                removeObserver(entry.getKey());
-            }
-        }
-    }
-
-    /**
-     * Posts a task to a main thread to set the given value. So if you have a following code
-     * executed in the main thread:
-     * <pre class="prettyprint">
-     * liveData.postValue("a");
-     * liveData.setValue("b");
-     * </pre>
-     * The value "b" would be set at first and later the main thread would override it with
-     * the value "a".
-     * <p>
-     * If you called this method multiple times before a main thread executed a posted task, only
-     * the last value would be dispatched.
-     *
-     * @param value The new value
-     */
-    protected void postValue(T value) {
-        boolean postTask;
-        synchronized (mDataLock) {
-            postTask = mPendingData == NOT_SET;
-            mPendingData = value;
-        }
-        if (!postTask) {
-            return;
-        }
-        AppToolkitTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
-    }
-
-    /**
-     * Sets the value. If there are active observers, the value will be dispatched to them.
-     * <p>
-     * This method must be called from the main thread. If you need set a value from a background
-     * thread, you can use {@link #postValue(Object)}
-     *
-     * @param value The new value
-     */
-    @MainThread
-    protected void setValue(T value) {
-        assertMainThread("setValue");
-        mVersion++;
-        mData = value;
-        dispatchingValue(null);
-    }
-
-    /**
-     * Returns the current value.
-     * Note that calling this method on a background thread does not guarantee that the latest
-     * value set will be received.
-     *
-     * @return the current value
-     */
-    @Nullable
-    public T getValue() {
-        Object data = mData;
-        if (data != NOT_SET) {
-            //noinspection unchecked
-            return (T) data;
-        }
-        return null;
-    }
-
-    int getVersion() {
-        return mVersion;
-    }
-
-    /**
-     * Called when the number of active observers change to 1 from 0.
-     * <p>
-     * This callback can be used to know that this LiveData is being used thus should be kept
-     * up to date.
-     */
-    protected void onActive() {
-
-    }
-
-    /**
-     * Called when the number of active observers change from 1 to 0.
-     * <p>
-     * This does not mean that there are no observers left, there may still be observers but their
-     * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
-     * (like an Activity in the back stack).
-     * <p>
-     * You can check if there are observers via {@link #hasObservers()}.
-     */
-    protected void onInactive() {
-
-    }
-
-    /**
-     * Returns true if this LiveData has observers.
-     *
-     * @return true if this LiveData has observers
-     */
-    public boolean hasObservers() {
-        return mObservers.size() > 0;
-    }
-
-    /**
-     * Returns true if this LiveData has active observers.
-     *
-     * @return true if this LiveData has active observers
-     */
-    public boolean hasActiveObservers() {
-        return mActiveCount > 0;
-    }
-
-    class LifecycleBoundObserver implements LifecycleObserver {
-        public final LifecycleOwner owner;
-        public final Observer<T> observer;
-        public boolean active;
-        public int lastVersion = START_VERSION;
-
-        LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
-            this.owner = owner;
-            this.observer = observer;
-        }
-
-        @SuppressWarnings("unused")
-        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-        void onStateChange() {
-            if (owner.getLifecycle().getCurrentState() == DESTROYED) {
-                removeObserver(observer);
-                return;
-            }
-            // immediately set active state, so we'd never dispatch anything to inactive
-            // owner
-            activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
-
-        }
-
-        void activeStateChanged(boolean newActive) {
-            if (newActive == active) {
-                return;
-            }
-            active = newActive;
-            boolean wasInactive = LiveData.this.mActiveCount == 0;
-            LiveData.this.mActiveCount += active ? 1 : -1;
-            if (wasInactive && active) {
-                onActive();
-            }
-            if (LiveData.this.mActiveCount == 0 && !active) {
-                onInactive();
-            }
-            if (active) {
-                dispatchingValue(this);
-            }
-        }
-    }
-
-    static boolean isActiveState(State state) {
-        return state.isAtLeast(STARTED);
-    }
-
-    private void assertMainThread(String methodName) {
-        if (!AppToolkitTaskExecutor.getInstance().isMainThread()) {
-            throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
-                    + " thread");
-        }
-    }
+public class LiveData<T> {
 }
diff --git a/android/arch/lifecycle/LiveDataReactiveStreams.java b/android/arch/lifecycle/LiveDataReactiveStreams.java
index 0be0149..2b25bc9 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -16,7 +16,8 @@
 
 package android.arch.lifecycle;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 import org.reactivestreams.Publisher;
@@ -85,7 +86,7 @@
                         if (n < 0 || mCanceled) {
                             return;
                         }
-                        AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
                             @Override
                             public void run() {
                                 if (mCanceled) {
@@ -110,7 +111,7 @@
                         if (mCanceled) {
                             return;
                         }
-                        AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
                             @Override
                             public void run() {
                                 if (mCanceled) {
@@ -133,40 +134,101 @@
 
     /**
      * Creates an Observable {@link LiveData} stream from a ReactiveStreams publisher.
+     *
+     * <p>
+     * When the LiveData becomes active, it subscribes to the emissions from the Publisher.
+     *
+     * <p>
+     * When the LiveData becomes inactive, the subscription is cleared.
+     * LiveData holds the last value emitted by the Publisher when the LiveData was active.
+     * <p>
+     * Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
+     * added, it will automatically notify with the last value held in LiveData,
+     * which might not be the last value emitted by the Publisher.
+     *
+     * @param <T> The type of data hold by this instance.
      */
     public static <T> LiveData<T> fromPublisher(final Publisher<T> publisher) {
-        MutableLiveData<T> liveData = new MutableLiveData<>();
-        // Since we don't have a way to directly observe cancels, weakly hold the live data.
-        final WeakReference<MutableLiveData<T>> liveDataRef = new WeakReference<>(liveData);
-
-        publisher.subscribe(new Subscriber<T>() {
-            @Override
-            public void onSubscribe(Subscription s) {
-                // Don't worry about backpressure. If the stream is too noisy then backpressure can
-                // be handled upstream.
-                s.request(Long.MAX_VALUE);
-            }
-
-            @Override
-            public void onNext(final T t) {
-                final LiveData<T> liveData = liveDataRef.get();
-                if (liveData != null) {
-                    liveData.postValue(t);
-                }
-            }
-
-            @Override
-            public void onError(Throwable t) {
-                // Errors should be handled upstream, so propagate as a crash.
-                throw new RuntimeException(t);
-            }
-
-            @Override
-            public void onComplete() {
-            }
-        });
-
-        return liveData;
+        return new PublisherLiveData<>(publisher);
     }
 
+    /**
+     * Defines a {@link LiveData} object that wraps a {@link Publisher}.
+     *
+     * <p>
+     * When the LiveData becomes active, it subscribes to the emissions from the Publisher.
+     *
+     * <p>
+     * When the LiveData becomes inactive, the subscription is cleared.
+     * LiveData holds the last value emitted by the Publisher when the LiveData was active.
+     * <p>
+     * Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
+     * added, it will automatically notify with the last value held in LiveData,
+     * which might not be the last value emitted by the Publisher.
+     *
+     * @param <T> The type of data hold by this instance.
+     */
+    private static class PublisherLiveData<T> extends LiveData<T> {
+        private WeakReference<Subscription> mSubscriptionRef;
+        private final Publisher mPublisher;
+        private final Object mLock = new Object();
+
+        PublisherLiveData(@NonNull final Publisher publisher) {
+            mPublisher = publisher;
+        }
+
+        @Override
+        protected void onActive() {
+            super.onActive();
+
+            mPublisher.subscribe(new Subscriber<T>() {
+                @Override
+                public void onSubscribe(Subscription s) {
+                    // Don't worry about backpressure. If the stream is too noisy then
+                    // backpressure can be handled upstream.
+                    synchronized (mLock) {
+                        s.request(Long.MAX_VALUE);
+                        mSubscriptionRef = new WeakReference<>(s);
+                    }
+                }
+
+                @Override
+                public void onNext(final T t) {
+                    postValue(t);
+                }
+
+                @Override
+                public void onError(Throwable t) {
+                    synchronized (mLock) {
+                        mSubscriptionRef = null;
+                    }
+                    // Errors should be handled upstream, so propagate as a crash.
+                    throw new RuntimeException(t);
+                }
+
+                @Override
+                public void onComplete() {
+                    synchronized (mLock) {
+                        mSubscriptionRef = null;
+                    }
+                }
+            });
+
+        }
+
+        @Override
+        protected void onInactive() {
+            super.onInactive();
+            synchronized (mLock) {
+                WeakReference<Subscription> subscriptionRef = mSubscriptionRef;
+                if (subscriptionRef != null) {
+                    Subscription subscription = subscriptionRef.get();
+                    if (subscription != null) {
+                        subscription.cancel();
+                    }
+                    mSubscriptionRef = null;
+                }
+            }
+        }
+    }
 }
diff --git a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
index 87fba27..7278847 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -16,12 +16,10 @@
 
 package android.arch.lifecycle;
 
-import static android.arch.lifecycle.Lifecycle.State.RESUMED;
-
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.support.annotation.Nullable;
 import android.support.test.filters.SmallTest;
@@ -47,28 +45,7 @@
 
 @SmallTest
 public class LiveDataReactiveStreamsTest {
-    private static final Lifecycle sLifecycle = new Lifecycle() {
-        @Override
-        public void addObserver(LifecycleObserver observer) {
-        }
-
-        @Override
-        public void removeObserver(LifecycleObserver observer) {
-        }
-
-        @Override
-        public State getCurrentState() {
-            return RESUMED;
-        }
-    };
-    private static final LifecycleOwner S_LIFECYCLE_OWNER = new LifecycleOwner() {
-
-        @Override
-        public Lifecycle getLifecycle() {
-            return sLifecycle;
-        }
-
-    };
+    private LifecycleOwner mLifecycleOwner;
 
     private final List<String> mLiveDataOutput = new ArrayList<>();
     private final Observer<String> mObserver = new Observer<String>() {
@@ -85,8 +62,19 @@
 
     @Before
     public void init() {
+        mLifecycleOwner = new LifecycleOwner() {
+            LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+            {
+                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+            }
+
+            @Override
+            public Lifecycle getLifecycle() {
+                return mRegistry;
+            }
+        };
         mTestThread = Thread.currentThread();
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
 
             @Override
             public void executeOnDiskIO(Runnable runnable) {
@@ -109,7 +97,7 @@
 
     @After
     public void removeExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     @Test
@@ -117,7 +105,7 @@
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
 
-        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+        liveData.observe(mLifecycleOwner, mObserver);
 
         processor.onNext("foo");
         processor.onNext("bar");
@@ -132,13 +120,13 @@
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
 
-        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+        liveData.observe(mLifecycleOwner, mObserver);
 
         processor.onNext("foo");
         processor.onNext("bar");
 
         // The second mObserver should only get the newest value and any later values.
-        liveData.observe(S_LIFECYCLE_OWNER, new Observer<String>() {
+        liveData.observe(mLifecycleOwner, new Observer<String>() {
             @Override
             public void onChanged(@Nullable String s) {
                 output2.add(s);
@@ -152,12 +140,44 @@
     }
 
     @Test
+    public void convertsFromPublisherAfterInactive() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+        processor.onNext("foo");
+        liveData.removeObserver(mObserver);
+        processor.onNext("bar");
+
+        liveData.observe(mLifecycleOwner, mObserver);
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherManagesSubcriptions() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        assertThat(processor.hasSubscribers(), is(false));
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        // once the live data is active, there's a subscriber
+        assertThat(processor.hasSubscribers(), is(true));
+
+        liveData.removeObserver(mObserver);
+        // once the live data is inactive, the subscriber is removed
+        assertThat(processor.hasSubscribers(), is(false));
+    }
+
+    @Test
     public void convertsFromAsyncPublisher() {
         Flowable<String> input = Flowable.just("foo")
                 .concatWith(Flowable.just("bar", "baz").observeOn(sBackgroundScheduler));
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(input);
 
-        liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+        liveData.observe(mLifecycleOwner, mObserver);
 
         assertThat(mLiveDataOutput, is(Collections.singletonList("foo")));
         sBackgroundScheduler.triggerActions();
@@ -170,7 +190,7 @@
         liveData.setValue("foo");
         assertThat(liveData.getValue(), is("foo"));
 
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .subscribe(mOutputProcessor);
 
         liveData.setValue("bar");
@@ -188,7 +208,7 @@
         assertThat(liveData.getValue(), is("foo"));
 
         Disposable disposable = Flowable
-                .fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+                .fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .subscribe(new Consumer<String>() {
                     @Override
                     public void accept(String s) throws Exception {
@@ -216,7 +236,7 @@
 
         final AsyncSubject<Subscription> subscriptionSubject = AsyncSubject.create();
 
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .subscribe(new Subscriber<String>() {
                     @Override
                     public void onSubscribe(Subscription s) {
@@ -275,7 +295,7 @@
     public void convertsToPublisherWithAsyncData() {
         MutableLiveData<String> liveData = new MutableLiveData<>();
 
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
                 .observeOn(sBackgroundScheduler)
                 .subscribe(mOutputProcessor);
 
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index ed2a35d..9f0b425 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -36,16 +36,20 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.lifecycle.util.InstantTaskExecutor;
 import android.support.annotation.Nullable;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
 import org.mockito.Mockito;
 
 @SuppressWarnings({"unchecked"})
+@RunWith(JUnit4.class)
 public class LiveDataTest {
     private PublicLiveData<String> mLiveData;
     private LifecycleOwner mOwner;
@@ -66,12 +70,12 @@
 
     @Before
     public void swapExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
 
     @After
     public void removeExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     @Test
@@ -418,6 +422,40 @@
         verify(mActiveObserversChanged, never()).onCall(anyBoolean());
     }
 
+    @Test
+    public void testRemoveDuringAddition() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        mLiveData.setValue("bla");
+        mLiveData.observeForever(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mLiveData.removeObserver(this);
+            }
+        });
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+        inOrder.verify(mActiveObserversChanged).onCall(true);
+        inOrder.verify(mActiveObserversChanged).onCall(false);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testRemoveDuringBringingUpToState() {
+        mLiveData.setValue("bla");
+        mLiveData.observeForever(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mLiveData.removeObserver(this);
+            }
+        });
+        mRegistry.handleLifecycleEvent(ON_RESUME);
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+        inOrder.verify(mActiveObserversChanged).onCall(true);
+        inOrder.verify(mActiveObserversChanged).onCall(false);
+        inOrder.verifyNoMoreInteractions();
+    }
+
     @SuppressWarnings("WeakerAccess")
     static class PublicLiveData<T> extends LiveData<T> {
         // cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveDataTest.java b/android/arch/lifecycle/MediatorLiveDataTest.java
index 3de3eee..e2eadbe 100644
--- a/android/arch/lifecycle/MediatorLiveDataTest.java
+++ b/android/arch/lifecycle/MediatorLiveDataTest.java
@@ -25,7 +25,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.lifecycle.util.InstantTaskExecutor;
 import android.support.annotation.Nullable;
 
@@ -69,7 +69,7 @@
 
     @Before
     public void swapExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
 
     @Test
diff --git a/android/arch/lifecycle/MethodCallsLogger.java b/android/arch/lifecycle/MethodCallsLogger.java
new file mode 100644
index 0000000..031e43e
--- /dev/null
+++ b/android/arch/lifecycle/MethodCallsLogger.java
@@ -0,0 +1,42 @@
+/*
+ * 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.support.annotation.RestrictTo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class MethodCallsLogger {
+    private Map<String, Integer> mCalledMethods = new HashMap<>();
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public boolean approveCall(String name, int type) {
+        Integer nullableMask = mCalledMethods.get(name);
+        int mask = nullableMask != null ? nullableMask : 0;
+        boolean wasCalled = (mask & type) != 0;
+        mCalledMethods.put(name, mask | type);
+        return !wasCalled;
+    }
+}
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index e80e11c..37bdcdb 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -37,6 +37,7 @@
 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.After;
 import org.junit.Rule;
@@ -78,7 +79,7 @@
 
     @Test
     public void testNavigation() throws Throwable {
-        LifecycleActivity firstActivity = setupObserverOnResume();
+        FragmentActivity firstActivity = setupObserverOnResume();
         Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
                 NavigationTestActivitySecond.class.getCanonicalName(), null, false);
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
@@ -88,15 +89,15 @@
         firstActivity.finish();
         firstActivity.startActivity(intent);
 
-        LifecycleActivity secondActivity = (LifecycleActivity) monitor.waitForActivity();
+        FragmentActivity secondActivity = (FragmentActivity) monitor.waitForActivity();
         assertThat("Failed to navigate", secondActivity, notNullValue());
         checkProcessObserverSilent(secondActivity);
     }
 
     @Test
     public void testRecreation() throws Throwable {
-        LifecycleActivity activity = setupObserverOnResume();
-        LifecycleActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
+        FragmentActivity activity = setupObserverOnResume();
+        FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
         assertThat("Failed to recreate", recreated, notNullValue());
         checkProcessObserverSilent(recreated);
     }
@@ -112,14 +113,15 @@
 
         NavigationTestActivityFirst activity = activityTestRule.getActivity();
         activity.startActivity(new Intent(activity, NavigationDialogActivity.class));
-        LifecycleActivity dialogActivity = (LifecycleActivity) monitor.waitForActivity();
+        FragmentActivity dialogActivity = (FragmentActivity) monitor.waitForActivity();
         checkProcessObserverSilent(dialogActivity);
 
         List<Event> events = Collections.synchronizedList(new ArrayList<>());
 
         LifecycleObserver collectingObserver = new LifecycleObserver() {
             @OnLifecycleEvent(Event.ON_ANY)
-            public void onStateChanged(LifecycleOwner provider, Event event) {
+            public void onStateChanged(@SuppressWarnings("unused") LifecycleOwner provider,
+                    Event event) {
                 events.add(event);
             }
         };
@@ -138,8 +140,8 @@
         dialogActivity.finish();
     }
 
-    private LifecycleActivity setupObserverOnResume() throws Throwable {
-        LifecycleActivity firstActivity = activityTestRule.getActivity();
+    private FragmentActivity setupObserverOnResume() throws Throwable {
+        FragmentActivity firstActivity = activityTestRule.getActivity();
         waitTillResumed(firstActivity, activityTestRule);
         addProcessObserver(mObserver);
         mObserver.mChangedState = false;
@@ -156,7 +158,7 @@
                 ProcessLifecycleOwner.get().getLifecycle().removeObserver(observer));
     }
 
-    private void checkProcessObserverSilent(LifecycleActivity activity) throws Throwable {
+    private void checkProcessObserverSilent(FragmentActivity activity) throws Throwable {
         waitTillResumed(activity, activityTestRule);
         assertThat(mObserver.mChangedState, is(false));
         activityTestRule.runOnUiThread(() ->
diff --git a/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java b/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
index 44815e6..f010ed8 100644
--- a/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
+++ b/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
@@ -16,204 +16,23 @@
 
 package android.arch.lifecycle;
 
+import android.arch.lifecycle.ClassesInfoCache.CallbackInfo;
 import android.arch.lifecycle.Lifecycle.Event;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
 /**
  * An internal implementation of {@link GenericLifecycleObserver} that relies on reflection.
  */
 class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
     private final Object mWrapped;
     private final CallbackInfo mInfo;
-    @SuppressWarnings("WeakerAccess")
-    static final Map<Class, CallbackInfo> sInfoCache = new HashMap<>();
 
     ReflectiveGenericLifecycleObserver(Object wrapped) {
         mWrapped = wrapped;
-        mInfo = getInfo(mWrapped.getClass());
+        mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass());
     }
 
     @Override
     public void onStateChanged(LifecycleOwner source, Event event) {
-        invokeCallbacks(mInfo, source, event);
+        mInfo.invokeCallbacks(source, event, mWrapped);
     }
-
-    private void invokeMethodsForEvent(List<MethodReference> handlers, LifecycleOwner source,
-            Event event) {
-        if (handlers != null) {
-            for (int i = handlers.size() - 1; i >= 0; i--) {
-                MethodReference reference = handlers.get(i);
-                invokeCallback(reference, source, event);
-            }
-        }
-    }
-
-    @SuppressWarnings("ConstantConditions")
-    private void invokeCallbacks(CallbackInfo info, LifecycleOwner source, Event event) {
-        invokeMethodsForEvent(info.mEventToHandlers.get(event), source, event);
-        invokeMethodsForEvent(info.mEventToHandlers.get(Event.ON_ANY), source, event);
-    }
-
-    private void invokeCallback(MethodReference reference, LifecycleOwner source, Event event) {
-        //noinspection TryWithIdenticalCatches
-        try {
-            switch (reference.mCallType) {
-                case CALL_TYPE_NO_ARG:
-                    reference.mMethod.invoke(mWrapped);
-                    break;
-                case CALL_TYPE_PROVIDER:
-                    reference.mMethod.invoke(mWrapped, source);
-                    break;
-                case CALL_TYPE_PROVIDER_WITH_EVENT:
-                    reference.mMethod.invoke(mWrapped, source, event);
-                    break;
-            }
-        } catch (InvocationTargetException e) {
-            throw new RuntimeException("Failed to call observer method", e.getCause());
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static CallbackInfo getInfo(Class klass) {
-        CallbackInfo existing = sInfoCache.get(klass);
-        if (existing != null) {
-            return existing;
-        }
-        existing = createInfo(klass);
-        return existing;
-    }
-
-    private static void verifyAndPutHandler(Map<MethodReference, Event> handlers,
-            MethodReference newHandler, Event newEvent, Class klass) {
-        Event event = handlers.get(newHandler);
-        if (event != null && newEvent != event) {
-            Method method = newHandler.mMethod;
-            throw new IllegalArgumentException(
-                    "Method " + method.getName() + " in " + klass.getName()
-                            + " already declared with different @OnLifecycleEvent value: previous"
-                            + " value " + event + ", new value " + newEvent);
-        }
-        if (event == null) {
-            handlers.put(newHandler, newEvent);
-        }
-    }
-
-    private static CallbackInfo createInfo(Class klass) {
-        Class superclass = klass.getSuperclass();
-        Map<MethodReference, Event> handlerToEvent = new HashMap<>();
-        if (superclass != null) {
-            CallbackInfo superInfo = getInfo(superclass);
-            if (superInfo != null) {
-                handlerToEvent.putAll(superInfo.mHandlerToEvent);
-            }
-        }
-
-        Method[] methods = klass.getDeclaredMethods();
-
-        Class[] interfaces = klass.getInterfaces();
-        for (Class intrfc : interfaces) {
-            for (Entry<MethodReference, Event> entry : getInfo(intrfc).mHandlerToEvent.entrySet()) {
-                verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
-            }
-        }
-
-        for (Method method : methods) {
-            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
-            if (annotation == null) {
-                continue;
-            }
-            Class<?>[] params = method.getParameterTypes();
-            int callType = CALL_TYPE_NO_ARG;
-            if (params.length > 0) {
-                callType = CALL_TYPE_PROVIDER;
-                if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
-                    throw new IllegalArgumentException(
-                            "invalid parameter type. Must be one and instanceof LifecycleOwner");
-                }
-            }
-            Event event = annotation.value();
-
-            if (params.length > 1) {
-                callType = CALL_TYPE_PROVIDER_WITH_EVENT;
-                if (!params[1].isAssignableFrom(Event.class)) {
-                    throw new IllegalArgumentException(
-                            "invalid parameter type. second arg must be an event");
-                }
-                if (event != Event.ON_ANY) {
-                    throw new IllegalArgumentException(
-                            "Second arg is supported only for ON_ANY value");
-                }
-            }
-            if (params.length > 2) {
-                throw new IllegalArgumentException("cannot have more than 2 params");
-            }
-            MethodReference methodReference = new MethodReference(callType, method);
-            verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
-        }
-        CallbackInfo info = new CallbackInfo(handlerToEvent);
-        sInfoCache.put(klass, info);
-        return info;
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class CallbackInfo {
-        final Map<Event, List<MethodReference>> mEventToHandlers;
-        final Map<MethodReference, Event> mHandlerToEvent;
-
-        CallbackInfo(Map<MethodReference, Event> handlerToEvent) {
-            mHandlerToEvent = handlerToEvent;
-            mEventToHandlers = new HashMap<>();
-            for (Entry<MethodReference, Event> entry : handlerToEvent.entrySet()) {
-                Event event = entry.getValue();
-                List<MethodReference> methodReferences = mEventToHandlers.get(event);
-                if (methodReferences == null) {
-                    methodReferences = new ArrayList<>();
-                    mEventToHandlers.put(event, methodReferences);
-                }
-                methodReferences.add(entry.getKey());
-            }
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class MethodReference {
-        final int mCallType;
-        final Method mMethod;
-
-        MethodReference(int callType, Method method) {
-            mCallType = callType;
-            mMethod = method;
-            mMethod.setAccessible(true);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            MethodReference that = (MethodReference) o;
-            return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName());
-        }
-
-        @Override
-        public int hashCode() {
-            return 31 * mCallType + mMethod.getName().hashCode();
-        }
-    }
-
-    private static final int CALL_TYPE_NO_ARG = 0;
-    private static final int CALL_TYPE_PROVIDER = 1;
-    private static final int CALL_TYPE_PROVIDER_WITH_EVENT = 2;
 }
diff --git a/android/arch/lifecycle/SingleGeneratedAdapterObserver.java b/android/arch/lifecycle/SingleGeneratedAdapterObserver.java
new file mode 100644
index 0000000..d176a3a
--- /dev/null
+++ b/android/arch/lifecycle/SingleGeneratedAdapterObserver.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 android.arch.lifecycle;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SingleGeneratedAdapterObserver implements GenericLifecycleObserver {
+
+    private final GeneratedAdapter mGeneratedAdapter;
+
+    SingleGeneratedAdapterObserver(GeneratedAdapter generatedAdapter) {
+        mGeneratedAdapter = generatedAdapter;
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+        mGeneratedAdapter.callMethods(source, event, false, null);
+        mGeneratedAdapter.callMethods(source, event, true, null);
+    }
+}
diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index c5a520f..f0214bf 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -23,15 +23,16 @@
 import android.app.Instrumentation.ActivityMonitor;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
 
 import java.util.concurrent.CountDownLatch;
 
-public class TestUtils {
+class TestUtils {
 
     private static final long TIMEOUT_MS = 2000;
 
     @SuppressWarnings("unchecked")
-    public static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
+    static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
             throws Throwable {
         ActivityMonitor monitor = new ActivityMonitor(
                 activity.getClass().getCanonicalName(), null, false);
@@ -60,7 +61,7 @@
         return result;
     }
 
-    static void waitTillResumed(final LifecycleActivity a, ActivityTestRule<?> activityRule)
+    static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
             throws Throwable {
         final CountDownLatch latch = new CountDownLatch(1);
         activityRule.runOnUiThread(() -> {
diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index c316563..9ce9cbb 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -22,6 +22,12 @@
 
 /**
  * Transformations for a {@link LiveData} class.
+ * <p>
+ * You can use transformation methods to carry information across the observer's lifecycle. The
+ * transformations aren't calculated unless an observer is observing the returned LiveData object.
+ * <p>
+ * Because the transformations are calculated lazily, lifecycle-related behavior is implicitly
+ * passed down without requiring additional explicit calls or dependencies.
  */
 @SuppressWarnings("WeakerAccess")
 public class Transformations {
@@ -34,6 +40,18 @@
      * LiveData and returns LiveData, which emits resulting values.
      * <p>
      * The given function {@code func} will be executed on the main thread.
+     * <p>
+     * Suppose that you have a LiveData, named {@code userLiveData}, that contains user data and you
+     * need to display the user name, created by concatenating the first and the last
+     * name of the user. You can define a function that handles the name creation, that will be
+     * applied to every value emitted by {@code useLiveData}.
+     *
+     * <pre>
+     * LiveData<User> userLiveData = ...;
+     * LiveData<String> userName = Transformations.map(userLiveData, user -> {
+     *      return user.firstName + " " + user.lastName
+     * });
+     * </pre>
      *
      * @param source a {@code LiveData} to listen to
      * @param func   a function to apply
@@ -63,9 +81,39 @@
      * <p>
      * If the given function returns null, then {@code swLiveData} is not "backed" by any other
      * LiveData.
+     *
      * <p>
      * The given function {@code func} will be executed on the main thread.
      *
+     * <p>
+     * Consider the case where you have a LiveData containing a user id. Every time there's a new
+     * user id emitted, you want to trigger a request to get the user object corresponding to that
+     * id, from a repository that also returns a LiveData.
+     * <p>
+     * The {@code userIdLiveData} is the trigger and the LiveData returned by the {@code
+     * repository.getUserById} is the "backing" LiveData.
+     * <p>
+     * In a scenario where the repository contains User(1, "Jane") and User(2, "John"), when the
+     * userIdLiveData value is set to "1", the {@code switchMap} will call {@code getUser(1)},
+     * that will return a LiveData containing the value User(1, "Jane"). So now, the userLiveData
+     * will emit User(1, "Jane"). When the user in the repository gets updated to User(1, "Sarah"),
+     * the {@code userLiveData} gets automatically notified and will emit User(1, "Sarah").
+     * <p>
+     * When the {@code setUserId} method is called with userId = "2", the value of the {@code
+     * userIdLiveData} changes and automatically triggers a request for getting the user with id
+     * "2" from the repository. So, the {@code userLiveData} emits User(2, "John"). The LiveData
+     * returned by {@code repository.getUserById(1)} is removed as a source.
+     *
+     * <pre>
+     * MutableLiveData<String> userIdLiveData = ...;
+     * LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, id ->
+     *     repository.getUserById(id));
+     *
+     * void setUserId(String userId) {
+     *      this.userIdLiveData.setValue(userId);
+     * }
+     * </pre>
+     *
      * @param trigger a {@code LiveData} to listen to
      * @param func    a function which creates "backing" LiveData
      * @param <X>     a type of {@code source} LiveData
diff --git a/android/arch/lifecycle/TransformationsTest.java b/android/arch/lifecycle/TransformationsTest.java
index e92ecca..940a3e8 100644
--- a/android/arch/lifecycle/TransformationsTest.java
+++ b/android/arch/lifecycle/TransformationsTest.java
@@ -25,7 +25,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.util.Function;
 import android.arch.lifecycle.util.InstantTaskExecutor;
 
@@ -42,7 +42,7 @@
 
     @Before
     public void swapExecutorDelegate() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
 
     @Before
diff --git a/android/arch/lifecycle/ViewModelProviderTest.java b/android/arch/lifecycle/ViewModelProviderTest.java
index 61760fc..8877357 100644
--- a/android/arch/lifecycle/ViewModelProviderTest.java
+++ b/android/arch/lifecycle/ViewModelProviderTest.java
@@ -21,6 +21,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import android.arch.lifecycle.ViewModelProvider.NewInstanceFactory;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -82,6 +84,18 @@
         assertThat(viewModel, is(provider.get(ViewModel1.class)));
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void testNotAttachedActivity() {
+        // This is similar to call ViewModelProviders.of in Activity's constructor
+        ViewModelProviders.of(new FragmentActivity());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotAttachedFragment() {
+        // This is similar to call ViewModelProviders.of in Activity's constructor
+        ViewModelProviders.of(new Fragment());
+    }
+
     public static class ViewModel1 extends ViewModel {
         boolean mCleared;
 
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index f64365b..746162a 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -17,6 +17,7 @@
 package android.arch.lifecycle;
 
 import android.annotation.SuppressLint;
+import android.app.Activity;
 import android.app.Application;
 import android.arch.lifecycle.ViewModelProvider.Factory;
 import android.support.annotation.MainThread;
@@ -40,6 +41,23 @@
         }
     }
 
+    private static Application checkApplication(Activity activity) {
+        Application application = activity.getApplication();
+        if (application == null) {
+            throw new IllegalStateException("Your activity/fragment is not yet attached to "
+                    + "Application. You can't request ViewModel before onCreate call.");
+        }
+        return application;
+    }
+
+    private static Activity checkActivity(Fragment fragment) {
+        Activity activity = fragment.getActivity();
+        if (activity == null) {
+            throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");
+        }
+        return activity;
+    }
+
     /**
      * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
      * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
@@ -51,12 +69,7 @@
      */
     @MainThread
     public static ViewModelProvider of(@NonNull Fragment fragment) {
-        FragmentActivity activity = fragment.getActivity();
-        if (activity == null) {
-            throw new IllegalArgumentException(
-                    "Can't create ViewModelProvider for detached fragment");
-        }
-        initializeFactoryIfNeeded(activity.getApplication());
+        initializeFactoryIfNeeded(checkApplication(checkActivity(fragment)));
         return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);
     }
 
@@ -71,7 +84,7 @@
      */
     @MainThread
     public static ViewModelProvider of(@NonNull FragmentActivity activity) {
-        initializeFactoryIfNeeded(activity.getApplication());
+        initializeFactoryIfNeeded(checkApplication(activity));
         return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
     }
 
@@ -87,6 +100,7 @@
      */
     @MainThread
     public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory) {
+        checkApplication(checkActivity(fragment));
         return new ViewModelProvider(ViewModelStores.of(fragment), factory);
     }
 
@@ -103,6 +117,7 @@
     @MainThread
     public static ViewModelProvider of(@NonNull FragmentActivity activity,
             @NonNull Factory factory) {
+        checkApplication(activity);
         return new ViewModelProvider(ViewModelStores.of(activity), factory);
     }
 
diff --git a/android/arch/lifecycle/ViewModelTest.java b/android/arch/lifecycle/ViewModelTest.java
index 98ce027..03ebdf3 100644
--- a/android/arch/lifecycle/ViewModelTest.java
+++ b/android/arch/lifecycle/ViewModelTest.java
@@ -32,6 +32,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 
@@ -120,7 +121,7 @@
             void onResume() {
                 try {
                     final FragmentManager manager = activity.getSupportFragmentManager();
-                    LifecycleFragment fragment = new LifecycleFragment();
+                    Fragment fragment = new Fragment();
                     manager.beginTransaction().add(fragment, "temp").commitNow();
                     ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class);
                     assertThat(vm.mCleared, is(false));
diff --git a/android/arch/lifecycle/activity/EmptyActivity.java b/android/arch/lifecycle/activity/EmptyActivity.java
index 017fff4..c32c898 100644
--- a/android/arch/lifecycle/activity/EmptyActivity.java
+++ b/android/arch/lifecycle/activity/EmptyActivity.java
@@ -16,13 +16,12 @@
 
 package android.arch.lifecycle.activity;
 
+import android.arch.lifecycle.extensions.test.R;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
 
-import android.arch.lifecycle.LifecycleActivity;
-import android.arch.lifecycle.extensions.test.R;
-
-public class EmptyActivity extends LifecycleActivity {
+public class EmptyActivity extends FragmentActivity {
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/android/arch/lifecycle/activity/FragmentLifecycleActivity.java b/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
index f4485e8..2eb1cc2 100644
--- a/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
+++ b/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
@@ -17,7 +17,6 @@
 package android.arch.lifecycle.activity;
 
 import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleFragment;
 import android.arch.lifecycle.LifecycleObserver;
 import android.arch.lifecycle.LifecycleOwner;
 import android.arch.lifecycle.OnLifecycleEvent;
@@ -26,6 +25,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
 import android.support.v7.app.AppCompatActivity;
 
 import java.util.ArrayList;
@@ -70,9 +70,9 @@
         mLoggedEvents.clear();
     }
 
-    public static class MainFragment extends LifecycleFragment {
+    public static class MainFragment extends Fragment {
         @Nullable
-        LifecycleFragment mNestedFragment;
+        Fragment mNestedFragment;
 
         @Override
         public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -85,7 +85,7 @@
         }
     }
 
-    public static class NestedFragment extends LifecycleFragment {
+    public static class NestedFragment extends Fragment {
     }
 
     public static Intent intentFor(Context context, boolean nested) {
@@ -98,7 +98,8 @@
         mObservedOwner = provider;
         provider.getLifecycle().addObserver(new LifecycleObserver() {
             @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-            public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+            public void anyEvent(@SuppressWarnings("unused") LifecycleOwner owner,
+                    Lifecycle.Event event) {
                 mLoggedEvents.add(event);
             }
         });
diff --git a/android/arch/lifecycle/observers/Base.java b/android/arch/lifecycle/observers/Base.java
new file mode 100644
index 0000000..08919d4
--- /dev/null
+++ b/android/arch/lifecycle/observers/Base.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class Base implements LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    public void onCreate() {
+    }
+}
diff --git a/android/arch/lifecycle/observers/Base_LifecycleAdapter.java b/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
new file mode 100644
index 0000000..4218b96
--- /dev/null
+++ b/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Base_LifecycleAdapter implements GeneratedAdapter {
+
+    public Base_LifecycleAdapter(Base base) {
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedSequence1.java b/android/arch/lifecycle/observers/DerivedSequence1.java
new file mode 100644
index 0000000..9db37f1
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedSequence1.java
@@ -0,0 +1,23 @@
+/*
+ * 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.observers;
+
+public class DerivedSequence1 extends Base {
+
+    public void something() {
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedSequence2.java b/android/arch/lifecycle/observers/DerivedSequence2.java
new file mode 100644
index 0000000..f2ef943
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedSequence2.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedSequence2 extends DerivedSequence1 {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    void onStop() {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithNewMethods.java b/android/arch/lifecycle/observers/DerivedWithNewMethods.java
new file mode 100644
index 0000000..b1eaef0
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithNewMethods.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithNewMethods extends Base {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    void onStop() {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java b/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
new file mode 100644
index 0000000..cb1afb8
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
@@ -0,0 +1,20 @@
+/*
+ * 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.observers;
+
+public class DerivedWithNoNewMethods extends Base {
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java b/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
new file mode 100644
index 0000000..40c7c9a
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
@@ -0,0 +1,29 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithOverridenMethodsWithLfAnnotation extends Base {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+}
diff --git a/android/arch/lifecycle/observers/Interface1.java b/android/arch/lifecycle/observers/Interface1.java
new file mode 100644
index 0000000..e193de9
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface1.java
@@ -0,0 +1,27 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public interface Interface1 extends LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    void onCreate();
+}
diff --git a/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java b/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
new file mode 100644
index 0000000..c597b1c
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Interface1_LifecycleAdapter implements GeneratedAdapter {
+
+    public Interface1_LifecycleAdapter(Interface1 base) {
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/Interface2.java b/android/arch/lifecycle/observers/Interface2.java
new file mode 100644
index 0000000..1056fcb
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface2.java
@@ -0,0 +1,30 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public interface Interface2 extends LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    void onCreate();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    void onDestroy();
+}
diff --git a/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java b/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
new file mode 100644
index 0000000..b05b41a
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Interface2_LifecycleAdapter implements GeneratedAdapter {
+
+    public Interface2_LifecycleAdapter(Interface2 base) {
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl1.java b/android/arch/lifecycle/observers/InterfaceImpl1.java
new file mode 100644
index 0000000..2f03393
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl1.java
@@ -0,0 +1,23 @@
+/*
+ * 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.observers;
+
+public class InterfaceImpl1 implements Interface1 {
+    @Override
+    public void onCreate() {
+    }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl2.java b/android/arch/lifecycle/observers/InterfaceImpl2.java
new file mode 100644
index 0000000..eef8ce4
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl2.java
@@ -0,0 +1,28 @@
+/*
+ * 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.observers;
+
+public class InterfaceImpl2 implements Interface1, Interface2 {
+    @Override
+    public void onCreate() {
+    }
+
+    @Override
+    public void onDestroy() {
+
+    }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl3.java b/android/arch/lifecycle/observers/InterfaceImpl3.java
new file mode 100644
index 0000000..8f31808
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl3.java
@@ -0,0 +1,23 @@
+/*
+ * 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.observers;
+
+public class InterfaceImpl3 extends Base implements Interface1 {
+    @Override
+    public void onCreate() {
+    }
+}
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
index 5972b16..5f33c28 100644
--- a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
@@ -19,8 +19,8 @@
 import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
 
 import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleActivity;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 import android.util.Pair;
 
 import java.util.ArrayList;
@@ -31,7 +31,7 @@
 /**
  * Activity for testing full lifecycle
  */
-public class FullLifecycleTestActivity extends LifecycleActivity implements CollectingActivity {
+public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
 
     private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
     private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
diff --git a/android/arch/lifecycle/testapp/LifecycleTestActivity.java b/android/arch/lifecycle/testapp/LifecycleTestActivity.java
index 093ec7f..cf07aee 100644
--- a/android/arch/lifecycle/testapp/LifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/LifecycleTestActivity.java
@@ -16,13 +16,13 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 
 /**
  * Activity for testing events by themselves
  */
-public class LifecycleTestActivity extends LifecycleActivity {
+public class LifecycleTestActivity extends FragmentActivity {
 
     /**
      * identifies that
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 709bd8d..0ae9403 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -16,10 +16,10 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
 
 /**
  *  an activity with Dialog theme.
  */
-public class NavigationDialogActivity extends LifecycleActivity {
+public class NavigationDialogActivity extends FragmentActivity {
 }
diff --git a/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java b/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
index f1847c9..69fd478 100644
--- a/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
+++ b/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
@@ -16,10 +16,10 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
 
 /**
  * Activity for ProcessOwnerTest
  */
-public class NavigationTestActivityFirst extends LifecycleActivity {
+public class NavigationTestActivityFirst extends FragmentActivity {
 }
diff --git a/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java b/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
index 221e927..0f9a4c9 100644
--- a/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
+++ b/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
@@ -16,10 +16,10 @@
 
 package android.arch.lifecycle.testapp;
 
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
 
 /**
  * Activity for ProcessOwnerTest
  */
-public class NavigationTestActivitySecond extends LifecycleActivity {
+public class NavigationTestActivitySecond extends FragmentActivity {
 }
diff --git a/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java b/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
index 6d61c5e..77bd99f 100644
--- a/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
@@ -17,12 +17,12 @@
 package android.arch.lifecycle.testapp;
 
 import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleActivity;
 import android.arch.lifecycle.LifecycleObserver;
 import android.arch.lifecycle.LifecycleOwner;
 import android.arch.lifecycle.OnLifecycleEvent;
 import android.arch.lifecycle.ProcessLifecycleOwner;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 import android.util.Pair;
 
 import java.util.ArrayList;
@@ -33,7 +33,7 @@
 /**
  * Activity for SimpleAppFullLifecycleTest
  */
-public class SimpleAppLifecycleTestActivity extends LifecycleActivity {
+public class SimpleAppLifecycleTestActivity extends FragmentActivity {
 
     public enum TestEventType {
         PROCESS_EVENT,
diff --git a/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java b/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
index 5ef9f16..1f9f100 100644
--- a/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
+++ b/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
@@ -16,15 +16,14 @@
 
 package android.arch.lifecycle.viewmodeltest;
 
+import android.arch.lifecycle.ViewModelProviders;
 import android.arch.lifecycle.extensions.test.R;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
 
-import android.arch.lifecycle.LifecycleActivity;
-import android.arch.lifecycle.LifecycleFragment;
-import android.arch.lifecycle.ViewModelProviders;
-
-public class ViewModelActivity extends LifecycleActivity {
+public class ViewModelActivity extends FragmentActivity {
     public static final String KEY_FRAGMENT_MODEL = "fragment-model";
     public static final String KEY_ACTIVITY_MODEL = "activity-model";
     public static final String FRAGMENT_TAG_1 = "f1";
@@ -47,7 +46,7 @@
         defaultActivityModel = ViewModelProviders.of(this).get(TestViewModel.class);
     }
 
-    public static class ViewModelFragment extends LifecycleFragment {
+    public static class ViewModelFragment extends Fragment {
         public TestViewModel fragmentModel;
         public TestViewModel activityModel;
         public TestViewModel defaultActivityModel;
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 96c23fc..664ab16 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -18,6 +18,7 @@
 
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.WorkerThread;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -49,15 +50,18 @@
      * @return List of loaded items. Null if the BoundedDataSource is no longer valid, and should
      *         not be queried again.
      */
+    @WorkerThread
     @Nullable
     public abstract List<Value> loadRange(int startPosition, int loadCount);
 
+    @WorkerThread
     @Nullable
     @Override
     public List<Value> loadAfter(int startIndex, int pageSize) {
         return loadRange(startIndex, pageSize);
     }
 
+    @WorkerThread
     @Nullable
     @Override
     public List<Value> loadBefore(int startIndex, int pageSize) {
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index 9ff1117..afcc208 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -41,6 +41,8 @@
         return true;
     }
 
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
     public abstract NullPaddedList<Value> loadInitial(
@@ -58,7 +60,10 @@
      * @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
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
     public final List<Value> loadAfter(int currentEndIndex,
@@ -88,7 +93,10 @@
      *                          on item contents.
      * @param pageSize          Suggested number of items to load.
      * @return List of items, in descending order, starting at position currentBeginIndex - 1.
+     *
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @WorkerThread
     @Nullable
     public final List<Value> loadBefore(int currentBeginIndex,
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 1e81569..48fbec5 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.WorkerThread;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -64,6 +65,7 @@
      * @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();
 
     /**
@@ -143,6 +145,7 @@
      *
      * @return True if the data source is invalid, and can no longer return data.
      */
+    @WorkerThread
     public boolean isInvalid() {
         return mInvalid.get();
     }
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 057eb7f..8cf6829 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -16,6 +16,7 @@
 
 package android.arch.paging;
 
+import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
@@ -54,7 +55,7 @@
  *     {@literal @}SuppressWarnings("FieldCanBeLocal")
  *     private final InvalidationTracker.Observer mObserver;
  *
- *     public OffsetUserQueryDataSource(MyDatabase db) {
+ *     public KeyedUserQueryDataSource(MyDatabase db) {
  *         mDb = db;
  *         mUserDao = db.getUserDao();
  *         mObserver = new InvalidationTracker.Observer("user") {
@@ -85,11 +86,15 @@
  *
  *     {@literal @}Override
  *     public List&lt;User> loadBefore({@literal @}NonNull String userName, int pageSize) {
+ *         // Return items adjacent to 'userName' in reverse order
+ *         // it's valid to return a different-sized list of items than pageSize, if it's easier
  *         return mUserDao.userNameLoadBefore(userName, pageSize);
  *     }
  *
  *     {@literal @}Override
  *     public List&lt;User> loadAfter({@literal @}Nullable String userName, int pageSize) {
+ *         // Return items adjacent to 'userName'
+ *         // it's valid to return a different-sized list of items than pageSize, if it's easier
  *         return mUserDao.userNameLoadAfter(userName, pageSize);
  *     }
  * }</pre>
@@ -198,13 +203,64 @@
         return list;
     }
 
+    /**
+     * Return a key associated with the given item.
+     * <p>
+     * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique
+     * integer ID, you would return {@code item.getID()} here. This key can then be passed to
+     * {@link #loadBefore(Key, int)} or {@link #loadAfter(Key, int)} to load additional items
+     * adjacent to the item passed to this function.
+     * <p>
+     * If your key is more complex, such as when you're sorting by name, then resolving collisions
+     * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
+     * such as {@code Pair<String, Integer>} or, in Kotlin,
+     * {@code data class Key(val name: String, val id: Int)}
+     *
+     * @param item Item to get the key from.
+     * @return Key associated with given item.
+     */
     @NonNull
+    @AnyThread
     public abstract Key getKey(@NonNull Value item);
 
+    /**
+     * Return the number of items that occur before the item uniquely identified by {@code key} in
+     * the data set.
+     * <p>
+     * For example, if you're loading items sorted by ID, then this would return the total number of
+     * items with ID less than {@code key}.
+     * <p>
+     * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsAfter(Key)}, your
+     * data source will not present placeholder null items in place of unloaded data.
+     *
+     * @param key A unique identifier of an item in the data set.
+     * @return Number of items in the data set before the item identified by {@code key}, or
+     *         {@link #COUNT_UNDEFINED}.
+     *
+     * @see #countItemsAfter(Key)
+     */
+    @WorkerThread
     public int countItemsBefore(@NonNull Key key) {
         return COUNT_UNDEFINED;
     }
 
+    /**
+     * Return the number of items that occur after the item uniquely identified by {@code key} in
+     * the data set.
+     * <p>
+     * For example, if you're loading items sorted by ID, then this would return the total number of
+     * items with ID greater than {@code key}.
+     * <p>
+     * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsBefore(Key)}, your
+     * data source will not present placeholder null items in place of unloaded data.
+     *
+     * @param key A unique identifier of an item in the data set.
+     * @return Number of items in the data set after the item identified by {@code key}, or
+     *         {@link #COUNT_UNDEFINED}.
+     *
+     * @see #countItemsBefore(Key)
+     */
+    @WorkerThread
     public int countItemsAfter(@NonNull Key key) {
         return COUNT_UNDEFINED;
     }
@@ -231,10 +287,17 @@
     public abstract List<Value> loadAfter(@NonNull Key currentEndKey, int pageSize);
 
     /**
-     * Load data before the currently loaded content, starting at the provided index.
+     * Load data before the currently loaded content, starting at the provided index,
+     * in reverse-display order.
      * <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 the number loaded than reduce.
+     * <p class="note"><strong>Note:</strong> Items returned from loadBefore <em>must</em> be in
+     * reverse order from how they will be presented in the list. The first item in the return list
+     * will be prepended immediately before the current beginning of the list. This is so that the
+     * KeyedDataSource may return a different number of items from the requested {@code pageSize} by
+     * shortening or lengthening the return list as it desires.
+     * <p>
      *
      * @param currentBeginKey Load items before this key.
      * @param pageSize         Suggested number of items to load.
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 0d5313d..6a31b68 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -27,16 +27,65 @@
 /**
  * Lazy loading list that pages in content from a {@link DataSource}.
  * <p>
- * A PagedList is a lazy loaded list, which presents data from a {@link DataSource}. If the
- * DataSource is counted (returns a valid number from its count method(s)), the PagedList will
- * present {@code null} items in place of not-yet-loaded content to serve as placeholders.
+ * A PagedList is a {@link List} which loads its data in chunks (pages) from a {@link DataSource}.
+ * Items can be accessed with {@link #get(int)}, and further loading can be triggered with
+ * {@link #loadAround(int)}. See {@link PagedListAdapter}, which enables the binding of a PagedList
+ * to a {@link android.support.v7.widget.RecyclerView}.
+ * <h4>Loading Data</h4>
  * <p>
- * When {@link #loadAround} is called, items will be loaded in near the passed position. If
- * placeholder {@code null}s are present in the list, they will be replaced as content is loaded.
+ * All data in a PagedList is loaded from its {@link DataSource}. Creating a PagedList loads data
+ * from the DataSource immediately, and should for this reason be done on a background thread. The
+ * constructed PagedList may then be passed to and used on the UI thread. This is done to prevent
+ * passing a list with no loaded content to the UI thread, which should generally not be presented
+ * to the user.
  * <p>
- * In this way, PagedList can present data for an unbounded, infinite scrolling list, or a very
- * large but countable list. See {@link PagedListAdapter}, which enables the binding of a PagedList
- * to a RecyclerView. Use {@link Config} to control how many items a PagedList loads, and when.
+ * When {@link #loadAround} is called, items will be loaded in near the passed list index. If
+ * placeholder {@code null}s are present in the list, they will be replaced as content is
+ * loaded. If not, newly loaded items will be inserted at the beginning or end of the list.
+ * <p>
+ * PagedList can present data for an unbounded, infinite scrolling list, or a very large but
+ * countable list. Use {@link Config} to control how many items a PagedList loads, and when.
+ * <p>
+ * If you use {@link LivePagedListProvider} to get a
+ * {@link android.arch.lifecycle.LiveData}&lt;PagedList>, it will initialize PagedLists on a
+ * background thread for you.
+ * <h4>Placeholders</h4>
+ * <p>
+ * There are two ways that PagedList can represent its not-yet-loaded data - with or without
+ * {@code null} placeholders.
+ * <p>
+ * With placeholders, the PagedList is always the full size of the data set. {@code get(N)} returns
+ * the {@code N}th item in the data set, or {@code null} if its not yet loaded.
+ * <p>
+ * Without {@code null} placeholders, the PagedList is the sublist of data that has already been
+ * loaded. The size of the PagedList is the number of currently loaded items, and {@code get(N)}
+ * returns the {@code N}th <em>loaded</em> item. This is not necessarily the {@code N}th item in the
+ * data set.
+ * <p>
+ * Placeholders have several benefits:
+ * <ul>
+ *     <li>They express the full sized list to the presentation layer (often a
+ *     {@link PagedListAdapter}), and so can support scrollbars (without jumping as pages are
+ *     loaded) and fast-scrolling to any position, whether loaded or not.
+ *     <li>They avoid the need for a loading spinner at the end of the loaded list, since the list
+ *     is always full sized.
+ * </ul>
+ * <p>
+ * They also have drawbacks:
+ * <ul>
+ *     <li>Your Adapter (or other presentation mechanism) needs to account for {@code null} items.
+ *     This often means providing default values in data you bind to a
+ *     {@link android.support.v7.widget.RecyclerView.ViewHolder}.
+ *     <li>They don't work well if your item views are of different sizes, as this will prevent
+ *     loading items from cross-fading nicely.
+ *     <li>They require you to count your data set, which can be expensive or impossible, depending
+ *     on where your data comes from.
+ * </ul>
+ * <p>
+ * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
+ * DataSource returns {@link DataSource#COUNT_UNDEFINED} from any item counting method, or if
+ * {@code false} is passed to {@link Config.Builder#setEnablePlaceholders(boolean)} when building a
+ * {@link Config}.
  *
  * @param <T> The type of the entries in the list.
  */
@@ -92,6 +141,18 @@
      * Builder class for PagedList.
      * <p>
      * DataSource, main thread and background executor, and Config must all be provided.
+     * <p>
+     * A valid PagedList may not be constructed without data, so building a PagedList queries
+     * initial data from the data source. This is done because it's generally undesired to present a
+     * PagedList with no data in it to the UI. It's better to present initial data, so that the UI
+     * doesn't show an empty list, or placeholders for a few frames, just before showing initial
+     * content.
+     * <p>
+     * Because PagedLists are initialized with data, PagedLists must be built on a background
+     * thread.
+     * <p>
+     * {@link LivePagedListProvider} does this creation on a background thread automatically, if you
+     * want to receive a {@code LiveData<PagedList<...>>}.
      *
      * @param <Key> Type of key used to load data from the DataSource.
      * @param <Value> Type of items held and loaded by the PagedList.
@@ -174,11 +235,17 @@
          * <p>
          * This call will initial data and perform any counting needed to initialize the PagedList,
          * therefore it should only be called on a worker thread.
+         * <p>
+         * While build() will always return a PagedList, it's important to note that the PagedList
+         * initial load may fail to acquire data from the DataSource. This can happen for example if
+         * the DataSource is invalidated during its initial load. If this happens, the PagedList
+         * will be immediately {@link PagedList#isDetached() detached}, and you can retry
+         * construction (including setting a new DataSource).
          *
          * @return The newly constructed PagedList
          */
-        @NonNull
         @WorkerThread
+        @NonNull
         public PagedList<Value> build() {
             if (mDataSource == null) {
                 throw new IllegalArgumentException("DataSource required");
@@ -403,6 +470,16 @@
              * Defines the number of items loaded at once from the DataSource.
              * <p>
              * Should be several times the number of visible items onscreen.
+             * <p>
+             * Configuring your page size depends on how your data is being loaded and used. Smaller
+             * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally
+             * improve loading throughput, to a point
+             * (avoid loading more than 2MB from SQLite at once, since it incurs extra cost).
+             * <p>
+             * If you're loading data for very large, social-media style cards that take up most of
+             * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're
+             * displaying dozens of items in a tiled grid, which can present items during a scroll
+             * much more quickly, consider closer to 100.
              *
              * @param pageSize Number of items loaded at once from the DataSource.
              * @return this
@@ -414,12 +491,15 @@
 
             /**
              * Defines how far from the edge of loaded content an access must be to trigger further
-             * loading. Defaults to page size.
-             * <p>
-             * A value of 0 indicates that no list items will be loaded before they are first
-             * requested.
+             * loading.
              * <p>
              * Should be several times the number of visible items onscreen.
+             * <p>
+             * If not set, defaults to page size.
+             * <p>
+             * A value of 0 indicates that no list items will be loaded until they are specifically
+             * requested. This is generally not recommended, so that users don't observe a
+             * placeholder item (with placeholders) or end of list (without) while scrolling.
              *
              * @param prefetchDistance Distance the PagedList should prefetch.
              * @return this
@@ -432,8 +512,10 @@
             /**
              * Pass false to disable null placeholders in PagedLists using this Config.
              * <p>
-             * A PagedList will present null placeholders for not yet loaded content if two
-             * contitions are met:
+             * If not set, defaults to true.
+             * <p>
+             * A PagedList will present null placeholders for not-yet-loaded content if two
+             * conditions are met:
              * <p>
              * 1) Its DataSource can count all unloaded items (so that the number of nulls to
              * present is known).
@@ -442,6 +524,13 @@
              * <p>
              * Call {@code setEnablePlaceholders(false)} to ensure the receiver of the PagedList
              * (often a {@link PagedListAdapter}) doesn't need to account for null items.
+             * <p>
+             * If placeholders are disabled, not-yet-loaded content will not be present in the list.
+             * Paging will still occur, but as items are loaded or removed, they will be signaled
+             * as inserts to the {@link PagedList.Callback}.
+             * {@link PagedList.Callback#onChanged(int, int)} will not be issued as part of loading,
+             * though a {@link PagedListAdapter} may still receive change events as a result of
+             * PagedList diffing.
              *
              * @param enablePlaceholders False if null placeholders should be disabled.
              * @return this
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 19a0c55..93c02ea 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -51,6 +51,7 @@
  *     public final LiveData&lt;PagedList&lt;User>> usersList;
  *     public MyViewModel(UserDao userDao) {
  *         usersList = userDao.usersByLastName().create(
+ *                 /* initial load position {@literal *}/ 0,
  *                 new PagedList.Config.Builder()
  *                         .setPageSize(50)
  *                         .setPrefetchDistance(50)
@@ -72,7 +73,7 @@
  *
  * class UserAdapter extends PagedListAdapter&lt;User, UserViewHolder> {
  *     public UserAdapter() {
- *         super(User.DIFF_CALLBACK);
+ *         super(DIFF_CALLBACK);
  *     }
  *     {@literal @}Override
  *     public void onBindViewHolder(UserViewHolder holder, int position) {
@@ -85,27 +86,21 @@
  *             holder.clear();
  *         }
  *     }
- * }
- *
- * {@literal @}Entity
- * class User {
- *      // ... simple POJO code omitted ...
- *
- *      public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
- *          {@literal @}Override
- *          public boolean areItemsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // User properties may have changed if reloaded from the DB, but ID is fixed
- *              return oldUser.getId() == newUser.getId();
- *          }
- *          {@literal @}Override
- *          public boolean areContentsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // NOTE: if you use equals, your object must properly override Object#equals()
- *              // Incorrectly returning false here will result in too many animations.
- *              return oldUser.equals(newUser);
- *          }
- *      }
+ *     public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
+ *         {@literal @}Override
+ *         public boolean areItemsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // User properties may have changed if reloaded from the DB, but ID is fixed
+ *             return oldUser.getId() == newUser.getId();
+ *         }
+ *         {@literal @}Override
+ *         public boolean areContentsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // NOTE: if you use equals, your object must properly override Object#equals()
+ *             // Incorrectly returning false here will result in too many animations.
+ *             return oldUser.equals(newUser);
+ *         }
+ *     }
  * }</pre>
  *
  * Advanced users that wish for more control over adapter behavior, or to provide a specific base
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index 4bff5fc..c7b61d9 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -57,6 +57,7 @@
  *     public final LiveData&lt;PagedList&lt;User>> usersList;
  *     public MyViewModel(UserDao userDao) {
  *         usersList = userDao.usersByLastName().create(
+ *                 /* initial load position {@literal *}/ 0,
  *                 new PagedList.Config.Builder()
  *                         .setPageSize(50)
  *                         .setPrefetchDistance(50)
@@ -79,7 +80,7 @@
  * class UserAdapter extends RecyclerView.Adapter&lt;UserViewHolder> {
  *     private final PagedListAdapterHelper&lt;User> mHelper;
  *     public UserAdapter(PagedListAdapterHelper.Builder&lt;User> builder) {
- *         mHelper = new PagedListAdapterHelper(this, User.DIFF_CALLBACK);
+ *         mHelper = new PagedListAdapterHelper(this, DIFF_CALLBACK);
  *     }
  *     {@literal @}Override
  *     public int getItemCount() {
@@ -99,13 +100,7 @@
  *             holder.clear();
  *         }
  *     }
- * }
- *
- * {@literal @}Entity
- * class User {
- *      // ... simple POJO code omitted ...
- *
- *      public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
+ *     public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
  *          {@literal @}Override
  *          public boolean areItemsTheSame(
  *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
diff --git a/android/arch/paging/TestExecutor.java b/android/arch/paging/TestExecutor.java
index 976f7df..30809c3 100644
--- a/android/arch/paging/TestExecutor.java
+++ b/android/arch/paging/TestExecutor.java
@@ -30,7 +30,7 @@
         mTasks.add(command);
     }
 
-    public boolean executeAll() {
+    boolean executeAll() {
         boolean consumed = !mTasks.isEmpty();
         Runnable task;
         while ((task = mTasks.poll()) != null) {
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 56556cd..36be423 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -17,6 +17,7 @@
 package android.arch.paging;
 
 import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
 
 import java.util.List;
 
@@ -90,6 +91,7 @@
      *
      * @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
      */
+    @WorkerThread
     @Override
     public abstract int countItems();
 
@@ -100,14 +102,20 @@
 
     /**
      * Called to load items at from the specified position range.
+     * <p>
+     * This method must return a list of requested size, unless at the end of list. Fixed size pages
+     * enable TiledDataSource to navigate tiles efficiently, and quickly accesss any position in the
+     * data set.
+     * <p>
+     * If a list of a different size is returned, but it is not the last list in the data set based
+     * on the return value from {@link #countItems()}, an exception will be thrown.
      *
      * @param startPosition Index of first item to load.
-     * @param count         Exact number of items to load. Returning a different number will cause
-     *                      an exception to be thrown.
-     * @return List of loaded items. Null if the DataSource is no longer valid, and should
-     *         not be queried again.
+     * @param count         Number of items to load.
+     * @return List of loaded items, of the requested length unless at end of list. Null if the
+     *         DataSource is no longer valid, and should not be queried again.
      */
-    @SuppressWarnings("WeakerAccess")
+    @WorkerThread
     public abstract List<Type> loadRange(int startPosition, int count);
 
     final List<Type> loadRangeWrapper(int startPosition, int count) {
@@ -132,6 +140,7 @@
             mTiledDataSource = tiledDataSource;
         }
 
+        @WorkerThread
         @Nullable
         @Override
         public List<Value> loadRange(int startPosition, int loadCount) {
diff --git a/android/arch/persistence/db/SupportSQLiteOpenHelper.java b/android/arch/persistence/db/SupportSQLiteOpenHelper.java
index 5a96e5a..02e4e7d 100644
--- a/android/arch/persistence/db/SupportSQLiteOpenHelper.java
+++ b/android/arch/persistence/db/SupportSQLiteOpenHelper.java
@@ -17,13 +17,18 @@
 package android.arch.persistence.db;
 
 import android.content.Context;
-import android.database.DatabaseErrorHandler;
-import android.database.DefaultDatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
 
 /**
  * An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}.
@@ -99,10 +104,29 @@
     void close();
 
     /**
-     * Matching callback methods from {@link android.database.sqlite.SQLiteOpenHelper}.
+     * Handles various lifecycle events for the SQLite connection, similar to
+     * {@link android.database.sqlite.SQLiteOpenHelper}.
      */
     @SuppressWarnings({"unused", "WeakerAccess"})
     abstract class Callback {
+        private static final String TAG = "SupportSQLite";
+        /**
+         * Version number of the database (starting at 1); if the database is older,
+         * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
+         * will be used to upgrade the database; if the database is newer,
+         * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
+         * will be used to downgrade the database.
+         */
+        public final int version;
+
+        /**
+         * Creates a new Callback to get database lifecycle events.
+         * @param version The version for the database instance. See {@link #version}.
+         */
+        public Callback(int version) {
+            this.version = version;
+        }
+
         /**
          * Called when the database connection is being configured, to enable features such as
          * write-ahead logging or foreign key support.
@@ -193,6 +217,81 @@
         public void onOpen(SupportSQLiteDatabase db) {
 
         }
+
+        /**
+         * The method invoked when database corruption is detected. Default implementation will
+         * delete the database file.
+         *
+         * @param db the {@link SupportSQLiteDatabase} object representing the database on which
+         *           corruption is detected.
+         */
+        public void onCorruption(SupportSQLiteDatabase db) {
+            // the following implementation is taken from {@link DefaultDatabaseErrorHandler}.
+
+            Log.e(TAG, "Corruption reported by sqlite on database: " + db.getPath());
+            // is the corruption detected even before database could be 'opened'?
+            if (!db.isOpen()) {
+                // database files are not even openable. delete this database file.
+                // NOTE if the database has attached databases, then any of them could be corrupt.
+                // and not deleting all of them could cause corrupted database file to remain and
+                // make the application crash on database open operation. To avoid this problem,
+                // the application should provide its own {@link DatabaseErrorHandler} impl class
+                // to delete ALL files of the database (including the attached databases).
+                deleteDatabaseFile(db.getPath());
+                return;
+            }
+
+            List<Pair<String, String>> attachedDbs = null;
+            try {
+                // Close the database, which will cause subsequent operations to fail.
+                // before that, get the attached database list first.
+                try {
+                    attachedDbs = db.getAttachedDbs();
+                } catch (SQLiteException e) {
+                /* ignore */
+                }
+                try {
+                    db.close();
+                } catch (IOException e) {
+                /* ignore */
+                }
+            } finally {
+                // Delete all files of this corrupt database and/or attached databases
+                if (attachedDbs != null) {
+                    for (Pair<String, String> p : attachedDbs) {
+                        deleteDatabaseFile(p.second);
+                    }
+                } else {
+                    // attachedDbs = null is possible when the database is so corrupt that even
+                    // "PRAGMA database_list;" also fails. delete the main database file
+                    deleteDatabaseFile(db.getPath());
+                }
+            }
+        }
+
+        private void deleteDatabaseFile(String fileName) {
+            if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
+                return;
+            }
+            Log.w(TAG, "deleting the database file: " + fileName);
+            try {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                    SQLiteDatabase.deleteDatabase(new File(fileName));
+                } else {
+                    try {
+                        final boolean deleted = new File(fileName).delete();
+                        if (!deleted) {
+                            Log.e(TAG, "Could not delete the database file " + fileName);
+                        }
+                    } catch (Exception error) {
+                        Log.e(TAG, "error while deleting corrupted database file", error);
+                    }
+                }
+            } catch (Exception e) {
+            /* print warning and ignore exception */
+                Log.w(TAG, "delete failed: ", e);
+            }
+        }
     }
 
     /**
@@ -211,33 +310,15 @@
         @Nullable
         public final String name;
         /**
-         * Version number of the database (starting at 1); if the database is older,
-         * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
-         * will be used to upgrade the database; if the database is newer,
-         * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
-         * will be used to downgrade the database.
-         */
-        public final int version;
-        /**
          * The callback class to handle creation, upgrade and downgrade.
          */
         @NonNull
         public final SupportSQLiteOpenHelper.Callback callback;
-        /**
-         * The {@link DatabaseErrorHandler} to be used when sqlite reports database
-         * corruption, or null to use the default error handler.
-         */
-        @Nullable
-        public final DatabaseErrorHandler errorHandler;
 
-        Configuration(@NonNull Context context, @Nullable String name,
-                int version, @Nullable DatabaseErrorHandler errorHandler,
-                @NonNull Callback callback) {
+        Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
             this.context = context;
             this.name = name;
-            this.version = version;
             this.callback = callback;
-            this.errorHandler = errorHandler;
         }
 
         /**
@@ -255,9 +336,7 @@
         public static class Builder {
             Context mContext;
             String mName;
-            int mVersion = 1;
             SupportSQLiteOpenHelper.Callback mCallback;
-            DatabaseErrorHandler mErrorHandler;
 
             public Configuration build() {
                 if (mCallback == null) {
@@ -268,11 +347,7 @@
                     throw new IllegalArgumentException("Must set a non-null context to create"
                             + " the configuration.");
                 }
-                if (mErrorHandler == null) {
-                    mErrorHandler = new DefaultDatabaseErrorHandler();
-                }
-                return new Configuration(mContext, mName, mVersion, mErrorHandler,
-                        mCallback);
+                return new Configuration(mContext, mName, mCallback);
             }
 
             Builder(@NonNull Context context) {
@@ -280,17 +355,6 @@
             }
 
             /**
-             * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite
-             *                     reports database corruption, or null to use the default error
-             *                     handler.
-             * @return This
-             */
-            public Builder errorHandler(@Nullable DatabaseErrorHandler errorHandler) {
-                mErrorHandler = errorHandler;
-                return this;
-            }
-
-            /**
              * @param name Name of the database file, or null for an in-memory database.
              * @return This
              */
@@ -307,20 +371,6 @@
                 mCallback = callback;
                 return this;
             }
-
-            /**
-             * @param version Version number of the database (starting at 1); if the database is
-             * older,
-             * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
-             * will be used to upgrade the database; if the database is newer,
-             * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
-             * will be used to downgrade the database.
-             * @return this
-             */
-            public Builder version(int version) {
-                mVersion = version;
-                return this;
-            }
         }
     }
 
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java b/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
index 92a5820..e9c2b74 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
@@ -53,8 +53,7 @@
      *
      * @param delegate The delegate to receive all calls.
      */
-    @SuppressWarnings("WeakerAccess")
-    public FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
+    FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
         mDelegate = delegate;
     }
 
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
index aa08fa4..a1690f4 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
@@ -28,42 +28,14 @@
 class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
     private final OpenHelper mDelegate;
 
-    FrameworkSQLiteOpenHelper(Context context, String name, int version,
-            DatabaseErrorHandler errorHandler,
-            SupportSQLiteOpenHelper.Callback callback) {
-        mDelegate = createDelegate(context, name, version, errorHandler, callback);
+    FrameworkSQLiteOpenHelper(Context context, String name,
+            Callback callback) {
+        mDelegate = createDelegate(context, name, callback);
     }
 
-    private OpenHelper createDelegate(Context context, String name,
-            int version, DatabaseErrorHandler errorHandler,
-            final Callback callback) {
-        return new OpenHelper(context, name, null, version, errorHandler) {
-            @Override
-            public void onCreate(SQLiteDatabase sqLiteDatabase) {
-                mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
-                callback.onCreate(mWrappedDb);
-            }
-
-            @Override
-            public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
-                callback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
-            }
-
-            @Override
-            public void onConfigure(SQLiteDatabase db) {
-                callback.onConfigure(getWrappedDb(db));
-            }
-
-            @Override
-            public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-                callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
-            }
-
-            @Override
-            public void onOpen(SQLiteDatabase db) {
-                callback.onOpen(getWrappedDb(db));
-            }
-        };
+    private OpenHelper createDelegate(Context context, String name, Callback callback) {
+        final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
+        return new OpenHelper(context, name, dbRef, callback);
     }
 
     @Override
@@ -92,14 +64,29 @@
         mDelegate.close();
     }
 
-    abstract static class OpenHelper extends SQLiteOpenHelper {
+    static class OpenHelper extends SQLiteOpenHelper {
+        /**
+         * This is used as an Object reference so that we can access the wrapped database inside
+         * the constructor. SQLiteOpenHelper requires the error handler to be passed in the
+         * constructor.
+         */
+        final FrameworkSQLiteDatabase[] mDbRef;
+        final Callback mCallback;
 
-        FrameworkSQLiteDatabase mWrappedDb;
-
-        OpenHelper(Context context, String name,
-                SQLiteDatabase.CursorFactory factory, int version,
-                DatabaseErrorHandler errorHandler) {
-            super(context, name, factory, version, errorHandler);
+        OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
+                final Callback callback) {
+            super(context, name, null, callback.version,
+                    new DatabaseErrorHandler() {
+                        @Override
+                        public void onCorruption(SQLiteDatabase dbObj) {
+                            FrameworkSQLiteDatabase db = dbRef[0];
+                            if (db != null) {
+                                callback.onCorruption(db);
+                            }
+                        }
+                    });
+            mCallback = callback;
+            mDbRef = dbRef;
         }
 
         SupportSQLiteDatabase getWritableSupportDatabase() {
@@ -113,16 +100,43 @@
         }
 
         FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
-            if (mWrappedDb == null) {
-                mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
+            FrameworkSQLiteDatabase dbRef = mDbRef[0];
+            if (dbRef == null) {
+                dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
+                mDbRef[0] = dbRef;
             }
-            return mWrappedDb;
+            return mDbRef[0];
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase sqLiteDatabase) {
+            mCallback.onCreate(getWrappedDb(sqLiteDatabase));
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
+            mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
+        }
+
+        @Override
+        public void onConfigure(SQLiteDatabase db) {
+            mCallback.onConfigure(getWrappedDb(db));
+        }
+
+        @Override
+        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
+        }
+
+        @Override
+        public void onOpen(SQLiteDatabase db) {
+            mCallback.onOpen(getWrappedDb(db));
         }
 
         @Override
         public synchronized void close() {
             super.close();
-            mWrappedDb = null;
+            mDbRef[0] = null;
         }
     }
 }
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
index 2268f45..ab11d49 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -27,8 +27,6 @@
     @Override
     public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
         return new FrameworkSQLiteOpenHelper(
-                configuration.context, configuration.name,
-                configuration.version, configuration.errorHandler, configuration.callback
-        );
+                configuration.context, configuration.name, configuration.callback);
     }
 }
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java b/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
index a2daf12..53a04bd 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
@@ -30,8 +30,7 @@
      *
      * @param delegate The SQLiteStatement to delegate calls to.
      */
-    @SuppressWarnings("WeakerAccess")
-    public FrameworkSQLiteStatement(SQLiteStatement delegate) {
+    FrameworkSQLiteStatement(SQLiteStatement delegate) {
         mDelegate = delegate;
     }
 
diff --git a/android/arch/persistence/room/Entity.java b/android/arch/persistence/room/Entity.java
index f54f0f8..94ca3bf 100644
--- a/android/arch/persistence/room/Entity.java
+++ b/android/arch/persistence/room/Entity.java
@@ -36,6 +36,9 @@
  * When a class is marked as an Entity, all of its fields are persisted. If you would like to
  * exclude some of its fields, you can mark them with {@link Ignore}.
  * <p>
+ * If a field is {@code transient}, it is automatically ignored <b>unless</b> it is annotated with
+ * {@link ColumnInfo}, {@link Embedded} or {@link Relation}.
+ * <p>
  * Example:
  * <pre>
  * {@literal @}Entity
diff --git a/android/arch/persistence/room/ForeignKey.java b/android/arch/persistence/room/ForeignKey.java
index 4ba0fb3..3ba632b 100644
--- a/android/arch/persistence/room/ForeignKey.java
+++ b/android/arch/persistence/room/ForeignKey.java
@@ -40,7 +40,7 @@
  * <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a> PRAGMA
  * to defer them depending on your transaction.
  * <p>
- * Please refer to the SQLite <a href="https://sqlite.org/foreignkeys.html>foreign keys</a>
+ * Please refer to the SQLite <a href="https://sqlite.org/foreignkeys.html">foreign keys</a>
  * documentation for details.
  */
 public @interface ForeignKey {
diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java
index 33bc4ed..45ec028 100644
--- a/android/arch/persistence/room/InvalidationTracker.java
+++ b/android/arch/persistence/room/InvalidationTracker.java
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.internal.SafeIterableMap;
 import android.arch.persistence.db.SupportSQLiteDatabase;
 import android.arch.persistence.db.SupportSQLiteStatement;
@@ -166,10 +166,12 @@
 
     private static void appendTriggerName(StringBuilder builder, String tableName,
             String triggerType) {
-        builder.append("room_table_modification_trigger_")
+        builder.append("`")
+                .append("room_table_modification_trigger_")
                 .append(tableName)
                 .append("_")
-                .append(triggerType);
+                .append(triggerType)
+                .append("`");
     }
 
     private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
@@ -192,9 +194,9 @@
             appendTriggerName(stringBuilder, tableName, trigger);
             stringBuilder.append(" AFTER ")
                     .append(trigger)
-                    .append(" ON ")
+                    .append(" ON `")
                     .append(tableName)
-                    .append(" BEGIN INSERT OR REPLACE INTO ")
+                    .append("` BEGIN INSERT OR REPLACE INTO ")
                     .append(UPDATE_TABLE_NAME)
                     .append(" VALUES(null, ")
                     .append(tableId)
@@ -238,7 +240,7 @@
             currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
         }
         if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
-            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+            ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
         }
     }
 
@@ -269,7 +271,7 @@
             wrapper = mObserverMap.remove(observer);
         }
         if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
-            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+            ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
         }
     }
 
@@ -350,11 +352,18 @@
                     return;
                 }
 
-                if (mDatabase.inTransaction()
-                        || !mPendingRefresh.compareAndSet(true, false)) {
+                if (!mPendingRefresh.compareAndSet(true, false)) {
                     // no pending refresh
                     return;
                 }
+
+                if (mDatabase.inTransaction()) {
+                    // current thread is in a transaction. when it ends, it will invoke
+                    // refreshRunnable again. mPendingRefresh is left as false on purpose
+                    // so that the last transaction can flip it on again.
+                    return;
+                }
+
                 mCleanupStatement.executeUpdateDelete();
                 mQueryArgs[0] = mMaxVersion;
                 Cursor cursor = mDatabase.query(SELECT_UPDATED_TABLES_SQL, mQueryArgs);
@@ -400,7 +409,7 @@
     public void refreshVersionsAsync() {
         // TODO we should consider doing this sync instead of async.
         if (mPendingRefresh.compareAndSet(false, true)) {
-            AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+            ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
         }
     }
 
diff --git a/android/arch/persistence/room/InvalidationTrackerTest.java b/android/arch/persistence/room/InvalidationTrackerTest.java
index f0b730a..d7474fd 100644
--- a/android/arch/persistence/room/InvalidationTrackerTest.java
+++ b/android/arch/persistence/room/InvalidationTrackerTest.java
@@ -247,7 +247,7 @@
         mTracker.mRefreshRunnable.run();
     }
 
-    @Test
+    // @Test - disabled due to flakiness b/65257997
     public void closedDbAfterOpen() throws InterruptedException {
         setVersions(3, 1);
         mTracker.addObserver(new LatchObserver(1, "a", "b"));
diff --git a/android/arch/persistence/room/RoomDatabase.java b/android/arch/persistence/room/RoomDatabase.java
index e64f2d6..cdad868 100644
--- a/android/arch/persistence/room/RoomDatabase.java
+++ b/android/arch/persistence/room/RoomDatabase.java
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.persistence.db.SimpleSQLiteQuery;
 import android.arch.persistence.db.SupportSQLiteDatabase;
 import android.arch.persistence.db.SupportSQLiteOpenHelper;
@@ -158,7 +158,7 @@
         if (mAllowMainThreadQueries) {
             return;
         }
-        if (AppToolkitTaskExecutor.getInstance().isMainThread()) {
+        if (ArchTaskExecutor.getInstance().isMainThread()) {
             throw new IllegalStateException("Cannot access database on the main thread since"
                     + " it may potentially lock the UI for a long period of time.");
         }
@@ -216,7 +216,11 @@
      */
     public void endTransaction() {
         mOpenHelper.getWritableDatabase().endTransaction();
-        mInvalidationTracker.refreshVersionsAsync();
+        if (!inTransaction()) {
+            // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
+            // endTransaction call to do it.
+            mInvalidationTracker.refreshVersionsAsync();
+        }
     }
 
     /**
@@ -311,7 +315,6 @@
         private ArrayList<Callback> mCallbacks;
 
         private SupportSQLiteOpenHelper.Factory mFactory;
-        private boolean mInMemory;
         private boolean mAllowMainThreadQueries;
         private boolean mRequireMigration;
         /**
@@ -381,6 +384,9 @@
         }
 
         /**
+         * Allows Room to destructively recreate database tables if {@link Migration}s that would
+         * migrate old database schemas to the latest schema version are not found.
+         * <p>
          * When the database version on the device does not match the latest schema version, Room
          * runs necessary {@link Migration}s on the database.
          * <p>
diff --git a/android/arch/persistence/room/RoomOpenHelper.java b/android/arch/persistence/room/RoomOpenHelper.java
index 8767f06..47279d6 100644
--- a/android/arch/persistence/room/RoomOpenHelper.java
+++ b/android/arch/persistence/room/RoomOpenHelper.java
@@ -44,6 +44,7 @@
 
     public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
             @NonNull String identityHash) {
+        super(delegate.version);
         mConfiguration = configuration;
         mDelegate = delegate;
         mIdentityHash = identityHash;
@@ -135,6 +136,12 @@
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public abstract static class Delegate {
+        public final int version;
+
+        public Delegate(int version) {
+            this.version = version;
+        }
+
         protected abstract void dropAllTables(SupportSQLiteDatabase database);
 
         protected abstract void createAllTables(SupportSQLiteDatabase database);
diff --git a/android/arch/persistence/room/RoomWarnings.java b/android/arch/persistence/room/RoomWarnings.java
index 91f32e4..c64be96 100644
--- a/android/arch/persistence/room/RoomWarnings.java
+++ b/android/arch/persistence/room/RoomWarnings.java
@@ -117,4 +117,12 @@
      */
     public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD =
             "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+
+    /**
+     * Reported when a Pojo has multiple constructors, one of which is a no-arg constructor. Room
+     * will pick that one by default but will print this warning in case the constructor choice is
+     * important. You can always guide Room to use the right constructor using the @Ignore
+     * annotation.
+     */
+    public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
 }
diff --git a/android/arch/persistence/room/RxRoom.java b/android/arch/persistence/room/RxRoom.java
index adfca27..285b3f8 100644
--- a/android/arch/persistence/room/RxRoom.java
+++ b/android/arch/persistence/room/RxRoom.java
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 
@@ -133,7 +133,7 @@
                 public Disposable schedule(@NonNull Runnable run, long delay,
                         @NonNull TimeUnit unit) {
                     DisposableRunnable disposable = new DisposableRunnable(run, mDisposed);
-                    AppToolkitTaskExecutor.getInstance().executeOnDiskIO(run);
+                    ArchTaskExecutor.getInstance().executeOnDiskIO(run);
                     return disposable;
                 }
 
diff --git a/android/arch/persistence/room/Transaction.java b/android/arch/persistence/room/Transaction.java
new file mode 100644
index 0000000..914e4f4
--- /dev/null
+++ b/android/arch/persistence/room/Transaction.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.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method in an abstract {@link Dao} class as a transaction method.
+ * <p>
+ * 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>
+ * Example:
+ * <pre>
+ * {@literal @}Dao
+ * public abstract class ProductDao {
+ *    {@literal @}Insert
+ *     public abstract void insert(Product product);
+ *    {@literal @}Delete
+ *     public abstract void delete(Product product);
+ *    {@literal @}Transaction
+ *     public void insertAndDeleteInTransaction(Product newProduct, Product oldProduct) {
+ *         // Anything inside this method runs in a single transaction.
+ *         insert(newProduct);
+ *         delete(oldProduct);
+ *     }
+ * }
+ * </pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface Transaction {
+}
diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index 1f434ad..320b2cd 100644
--- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -17,7 +17,7 @@
 package android.arch.persistence.room.integration.testapp;
 
 import android.app.Application;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.lifecycle.AndroidViewModel;
 import android.arch.lifecycle.LiveData;
 import android.arch.paging.DataSource;
@@ -47,7 +47,7 @@
         mDatabase = Room.databaseBuilder(this.getApplication(),
                 SampleDatabase.class, "customerDatabase").build();
 
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
             @Override
             public void run() {
                 // fill with some simple data
@@ -73,7 +73,7 @@
     }
 
     void insertCustomer() {
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
             @Override
             public void run() {
                 mDatabase.getCustomerDao().insert(createCustomer());
diff --git a/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java b/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
index e61d808..63b9507 100644
--- a/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
@@ -23,13 +23,18 @@
 import android.arch.persistence.room.RoomDatabase;
 import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
 import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.ObjectPKeyEntity;
 
 import java.util.List;
 
-@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class}, version = 1,
+@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class,
+        ObjectPKeyEntity.class, IntegerPKeyEntity.class}, version = 1,
         exportSchema = false)
 public abstract class PKeyTestDatabase extends RoomDatabase {
     public abstract IntPKeyDao intPKeyDao();
+    public abstract IntegerAutoIncPKeyDao integerAutoIncPKeyDao();
+    public abstract ObjectPKeyDao objectPKeyDao();
     public abstract IntegerPKeyDao integerPKeyDao();
 
     @Dao
@@ -50,9 +55,10 @@
     }
 
     @Dao
-    public interface IntegerPKeyDao {
+    public interface IntegerAutoIncPKeyDao {
         @Insert
-        void insertMe(IntegerAutoIncPKeyEntity items);
+        void insertMe(IntegerAutoIncPKeyEntity item);
+
         @Query("select * from IntegerAutoIncPKeyEntity WHERE pKey = :key")
         IntegerAutoIncPKeyEntity getMe(int key);
 
@@ -65,4 +71,19 @@
         @Query("select data from IntegerAutoIncPKeyEntity WHERE pKey IN(:ids)")
         List<String> loadDataById(long... ids);
     }
+
+    @Dao
+    public interface ObjectPKeyDao {
+        @Insert
+        void insertMe(ObjectPKeyEntity item);
+    }
+
+    @Dao
+    public interface IntegerPKeyDao {
+        @Insert
+        void insertMe(IntegerPKeyEntity item);
+
+        @Query("select * from IntegerPKeyEntity")
+        List<IntegerPKeyEntity> loadAll();
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/TestDatabase.java b/android/arch/persistence/room/integration/testapp/TestDatabase.java
index 9417296..2fad7b1 100644
--- a/android/arch/persistence/room/integration/testapp/TestDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/TestDatabase.java
@@ -21,6 +21,7 @@
 import android.arch.persistence.room.TypeConverter;
 import android.arch.persistence.room.TypeConverters;
 import android.arch.persistence.room.integration.testapp.dao.BlobEntityDao;
+import android.arch.persistence.room.integration.testapp.dao.FunnyNamedDao;
 import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
 import android.arch.persistence.room.integration.testapp.dao.PetDao;
 import android.arch.persistence.room.integration.testapp.dao.ProductDao;
@@ -31,6 +32,7 @@
 import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
 import android.arch.persistence.room.integration.testapp.dao.WithClauseDao;
 import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
 import android.arch.persistence.room.integration.testapp.vo.Pet;
 import android.arch.persistence.room.integration.testapp.vo.PetCouple;
 import android.arch.persistence.room.integration.testapp.vo.Product;
@@ -41,7 +43,7 @@
 import java.util.Date;
 
 @Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
-        BlobEntity.class, Product.class},
+        BlobEntity.class, Product.class, FunnyNamedEntity.class},
         version = 1, exportSchema = false)
 @TypeConverters(TestDatabase.Converters.class)
 public abstract class TestDatabase extends RoomDatabase {
@@ -55,6 +57,7 @@
     public abstract ProductDao getProductDao();
     public abstract SpecificDogDao getSpecificDogDao();
     public abstract WithClauseDao getWithClauseDao();
+    public abstract FunnyNamedDao getFunnyNamedDao();
 
     @SuppressWarnings("unused")
     public static class Converters {
diff --git a/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java b/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
new file mode 100644
index 0000000..93b5e72
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
@@ -0,0 +1,50 @@
+/*
+ * 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.dao;
+
+import static android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity.COLUMN_ID;
+import static android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity.TABLE_NAME;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
+
+import java.util.List;
+
+@Dao
+public interface FunnyNamedDao {
+    String SELECT_ONE = "select * from \"" +  TABLE_NAME + "\" WHERE \"" + COLUMN_ID + "\" = :id";
+    @Insert
+    void insert(FunnyNamedEntity... entities);
+    @Delete
+    void delete(FunnyNamedEntity... entities);
+    @Update
+    void update(FunnyNamedEntity... entities);
+
+    @Query("select * from \"" +  TABLE_NAME + "\" WHERE \"" + COLUMN_ID + "\" IN (:ids)")
+    List<FunnyNamedEntity> loadAll(int... ids);
+
+    @Query(SELECT_ONE)
+    LiveData<FunnyNamedEntity> observableOne(int id);
+
+    @Query(SELECT_ONE)
+    FunnyNamedEntity load(int id);
+}
diff --git a/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java b/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
index 7bb137f..18e8d93 100644
--- a/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
@@ -35,16 +35,16 @@
     @Query("SELECT * from School WHERE address_street LIKE '%' || :street || '%'")
     public abstract List<School> findByStreet(String street);
 
-    @Query("SELECT mName, manager_mName FROM School")
+    @Query("SELECT mId, mName, manager_mName FROM School")
     public abstract List<School> schoolAndManagerNames();
 
-    @Query("SELECT mName, manager_mName FROM School")
+    @Query("SELECT mId, mName, manager_mName FROM School")
     @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
     public abstract List<SchoolRef> schoolAndManagerNamesAsPojo();
 
     @Query("SELECT address_lat as lat, address_lng as lng FROM School WHERE mId = :schoolId")
     public abstract Coordinates loadCoordinates(int schoolId);
 
-    @Query("SELECT address_lat, address_lng FROM School WHERE mId = :schoolId")
+    @Query("SELECT mId, address_lat, address_lng FROM School WHERE mId = :schoolId")
     public abstract School loadCoordinatesAsSchool(int schoolId);
 }
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 337c233..665a1ae 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -24,6 +24,7 @@
 import android.arch.persistence.room.Insert;
 import android.arch.persistence.room.OnConflictStrategy;
 import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Transaction;
 import android.arch.persistence.room.Update;
 import android.arch.persistence.room.integration.testapp.TestDatabase;
 import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
@@ -259,4 +260,10 @@
             + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))"
             + " ORDER BY mLastName ASC, mName DESC, mId ASC")
     public abstract int userComplexCountBefore(String lastName, String name, int id);
+
+    @Transaction
+    public void insertBothByAnnotation(final User a, final User b) {
+        insert(a);
+        insert(b);
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java b/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
index 3507aee..eb15901 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
@@ -33,6 +33,8 @@
 
 import java.util.List;
 
+import io.reactivex.Flowable;
+
 @Dao
 public interface UserPetDao {
     @Query("SELECT * FROM User u, Pet p WHERE u.mId = p.mUserId")
@@ -62,6 +64,9 @@
     @Query("SELECT * FROM User u where u.mId = :userId")
     LiveData<UserAndAllPets> liveUserWithPets(int userId);
 
+    @Query("SELECT * FROM User u where u.mId = :userId")
+    Flowable<UserAndAllPets> flowableUserWithPets(int userId);
+
     @Query("SELECT * FROM User u where u.mId = :uid")
     EmbeddedUserAndAllPets loadUserAndPetsAsEmbedded(int uid);
 
diff --git a/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java b/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
index b1c38ed..40098ed 100644
--- a/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
@@ -16,13 +16,16 @@
 
 package android.arch.persistence.room.integration.testapp.dao;
 
+import android.annotation.TargetApi;
 import android.arch.lifecycle.LiveData;
 import android.arch.persistence.room.Dao;
 import android.arch.persistence.room.Query;
+import android.os.Build;
 
 import java.util.List;
 
 @Dao
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public interface WithClauseDao {
     @Query("WITH RECURSIVE factorial(n, fact) AS \n"
             + "(SELECT 0, 1 \n"
diff --git a/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java b/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
index eec59f6..9020eb1 100644
--- a/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
@@ -18,10 +18,6 @@
 
 import android.arch.persistence.room.Database;
 import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.TypeConverter;
-import android.arch.persistence.room.TypeConverters;
-
-import java.util.Date;
 
 /**
  * Sample database of customers.
diff --git a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
index 725d53f..7fe2bc9 100644
--- a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
+++ b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
@@ -318,6 +318,8 @@
                     + " (`id` INTEGER NOT NULL, `name` TEXT COLLATE NOCASE, PRIMARY KEY(`id`),"
                     + " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
                     + " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
+            database.execSQL("CREATE UNIQUE INDEX `index_entity1` ON "
+                    + MigrationDb.Entity1.TABLE_NAME + " (`name`)");
         }
     };
 
diff --git a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java b/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
index 4c9d73e..df70a17 100644
--- a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
+++ b/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
@@ -21,17 +21,17 @@
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.testing.CountingTaskExecutorRule;
 import android.arch.lifecycle.Lifecycle;
 import android.arch.lifecycle.LifecycleOwner;
 import android.arch.lifecycle.LifecycleRegistry;
 import android.arch.lifecycle.LiveData;
 import android.arch.lifecycle.Observer;
+import android.arch.paging.PagedList;
 import android.arch.persistence.room.integration.testapp.test.TestDatabaseTest;
 import android.arch.persistence.room.integration.testapp.test.TestUtil;
 import android.arch.persistence.room.integration.testapp.vo.User;
-import android.arch.paging.PagedList;
 import android.support.annotation.Nullable;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -131,7 +131,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(futureTask);
+        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
         futureTask.get();
     }
 
@@ -155,7 +155,7 @@
 
     private static class PagedListObserver<T> implements Observer<PagedList<T>> {
         private PagedList<T> mList;
-        public void reset() {
+        void reset() {
             mList = null;
         }
 
diff --git a/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java b/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
index 353c2e3..6f44546 100644
--- a/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
@@ -55,7 +55,6 @@
         Customer customer = new Customer();
         for (int i = 0; i < 100; i++) {
             SampleDatabase db = builder.build();
-            customer.setId(i);
             db.getCustomerDao().insert(customer);
             // Give InvalidationTracker enough time to start #mRefreshRunnable and pass the
             // initialization check.
diff --git a/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java b/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
new file mode 100644
index 0000000..f4fca7f
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.Observer;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FunnyNamedDaoTest extends TestDatabaseTest {
+    @Rule
+    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+
+    @Test
+    public void readWrite() {
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        FunnyNamedEntity loaded = mFunnyNamedDao.load(1);
+        assertThat(loaded, is(entity));
+    }
+
+    @Test
+    public void update() {
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        entity.setValue("b");
+        mFunnyNamedDao.update(entity);
+        FunnyNamedEntity loaded = mFunnyNamedDao.load(1);
+        assertThat(loaded.getValue(), is("b"));
+    }
+
+    @Test
+    public void delete() {
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        assertThat(mFunnyNamedDao.load(1), notNullValue());
+        mFunnyNamedDao.delete(entity);
+        assertThat(mFunnyNamedDao.load(1), nullValue());
+    }
+
+    @Test
+    public void observe() throws TimeoutException, InterruptedException {
+        final FunnyNamedEntity[] item = new FunnyNamedEntity[1];
+        mFunnyNamedDao.observableOne(2).observeForever(new Observer<FunnyNamedEntity>() {
+            @Override
+            public void onChanged(@Nullable FunnyNamedEntity funnyNamedEntity) {
+                item[0] = funnyNamedEntity;
+            }
+        });
+
+        FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+        mFunnyNamedDao.insert(entity);
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+        assertThat(item[0], nullValue());
+
+        final FunnyNamedEntity entity2 = new FunnyNamedEntity(2, "b");
+        mFunnyNamedDao.insert(entity2);
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+        assertThat(item[0], is(entity2));
+
+        final FunnyNamedEntity entity3 = new FunnyNamedEntity(2, "c");
+        mFunnyNamedDao.update(entity3);
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+        assertThat(item[0], is(entity3));
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
index 4787ce5..84f20ec 100644
--- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -21,7 +21,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.arch.persistence.room.InvalidationTracker;
 import android.arch.persistence.room.Room;
@@ -68,7 +68,7 @@
 
     @Before
     public void setSingleThreadedIO() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
             ExecutorService mIOExecutor = Executors.newSingleThreadExecutor();
             Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -91,7 +91,7 @@
 
     @After
     public void clearExecutor() {
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     private void waitUntilIOThreadIsIdle() {
@@ -101,7 +101,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnDiskIO(future);
+        ArchTaskExecutor.getInstance().executeOnDiskIO(future);
         //noinspection TryWithIdenticalCatches
         try {
             future.get();
diff --git a/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java b/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
index cae8445..d78411f 100644
--- a/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
@@ -21,7 +21,7 @@
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.testing.CountingTaskExecutorRule;
 import android.arch.lifecycle.Lifecycle;
 import android.arch.lifecycle.LifecycleOwner;
@@ -35,9 +35,11 @@
 import android.arch.persistence.room.integration.testapp.vo.Toy;
 import android.arch.persistence.room.integration.testapp.vo.User;
 import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
+import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -235,6 +237,7 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
     public void withWithClause() throws ExecutionException, InterruptedException,
             TimeoutException {
         LiveData<List<String>> actual =
@@ -322,7 +325,7 @@
                 return null;
             }
         });
-        AppToolkitTaskExecutor.getInstance().executeOnMainThread(futureTask);
+        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
         futureTask.get();
     }
 
diff --git a/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java b/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
index 97ce10c..fda4373 100644
--- a/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
@@ -16,29 +16,35 @@
 
 package android.arch.persistence.room.integration.testapp.test;
 
+import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import static org.junit.Assert.assertNotNull;
 
 import android.arch.persistence.room.Room;
 import android.arch.persistence.room.integration.testapp.PKeyTestDatabase;
 import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
 import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.ObjectPKeyEntity;
+import android.database.sqlite.SQLiteConstraintException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PrimaryKeyTest {
     private PKeyTestDatabase mDatabase;
+
     @Before
     public void setup() {
         mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
@@ -49,8 +55,8 @@
     public void integerTest() {
         IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
         entity.data = "foo";
-        mDatabase.integerPKeyDao().insertMe(entity);
-        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(1);
+        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(1);
         assertThat(loaded, notNullValue());
         assertThat(loaded.data, is(entity.data));
     }
@@ -60,8 +66,8 @@
         IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
         entity.pKey = 0;
         entity.data = "foo";
-        mDatabase.integerPKeyDao().insertMe(entity);
-        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(0);
+        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(0);
         assertThat(loaded, notNullValue());
         assertThat(loaded.data, is(entity.data));
     }
@@ -98,8 +104,8 @@
     public void getInsertedIdFromInteger() {
         IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
         entity.data = "foo";
-        final long id = mDatabase.integerPKeyDao().insertAndGetId(entity);
-        assertThat(mDatabase.integerPKeyDao().getMe((int) id).data, is("foo"));
+        final long id = mDatabase.integerAutoIncPKeyDao().insertAndGetId(entity);
+        assertThat(mDatabase.integerAutoIncPKeyDao().getMe((int) id).data, is("foo"));
     }
 
     @Test
@@ -108,7 +114,34 @@
         entity.data = "foo";
         IntegerAutoIncPKeyEntity entity2 = new IntegerAutoIncPKeyEntity();
         entity2.data = "foo2";
-        final long[] ids = mDatabase.integerPKeyDao().insertAndGetIds(entity, entity2);
-        assertThat(mDatabase.integerPKeyDao().loadDataById(ids), is(Arrays.asList("foo", "foo2")));
+        final long[] ids = mDatabase.integerAutoIncPKeyDao().insertAndGetIds(entity, entity2);
+        assertThat(mDatabase.integerAutoIncPKeyDao().loadDataById(ids),
+                is(Arrays.asList("foo", "foo2")));
+    }
+
+    @Test
+    public void insertNullPrimaryKey() throws Exception {
+        ObjectPKeyEntity o1 = new ObjectPKeyEntity(null, "1");
+
+        Throwable throwable = null;
+        try {
+            mDatabase.objectPKeyDao().insertMe(o1);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertNotNull("Was expecting an exception", throwable);
+        assertThat(throwable, instanceOf(SQLiteConstraintException.class));
+    }
+
+    @Test
+    public void insertNullPrimaryKeyForInteger() throws Exception {
+        IntegerPKeyEntity entity = new IntegerPKeyEntity();
+        entity.data = "data";
+        mDatabase.integerPKeyDao().insertMe(entity);
+
+        List<IntegerPKeyEntity> list = mDatabase.integerPKeyDao().loadAll();
+        assertThat(list.size(), is(1));
+        assertThat(list.get(0).data, is("data"));
+        assertNotNull(list.get(0).pKey);
     }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java b/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
index 1bbc140..01d071e 100644
--- a/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
+++ b/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
@@ -19,10 +19,12 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.arch.persistence.room.EmptyResultSetException;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
 import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -38,6 +40,7 @@
 import java.util.List;
 
 import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Predicate;
 import io.reactivex.observers.TestObserver;
 import io.reactivex.schedulers.TestScheduler;
 import io.reactivex.subscribers.TestSubscriber;
@@ -52,7 +55,7 @@
     public void setupSchedulers() {
         mTestScheduler = new TestScheduler();
         mTestScheduler.start();
-        AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
             @Override
             public void executeOnDiskIO(Runnable runnable) {
                 mTestScheduler.scheduleDirect(runnable);
@@ -73,7 +76,7 @@
     @After
     public void clearSchedulers() {
         mTestScheduler.shutdown();
-        AppToolkitTaskExecutor.getInstance().setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
     }
 
     private void drain() throws InterruptedException {
@@ -269,4 +272,60 @@
         subscriber.cancel();
         subscriber.assertNoErrors();
     }
+
+    @Test
+    public void flowableWithRelation() throws InterruptedException {
+        final TestSubscriber<UserAndAllPets> subscriber = new TestSubscriber<>();
+
+        mUserPetDao.flowableUserWithPets(3).subscribe(subscriber);
+        drain();
+        subscriber.assertSubscribed();
+
+        drain();
+        subscriber.assertNoValues();
+
+        final User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        subscriber.assertValue(new Predicate<UserAndAllPets>() {
+            @Override
+            public boolean test(UserAndAllPets userAndAllPets) throws Exception {
+                return userAndAllPets.user.equals(user);
+            }
+        });
+        subscriber.assertValueCount(1);
+        final Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mPetDao.insertAll(pets);
+        drain();
+        subscriber.assertValueAt(1, new Predicate<UserAndAllPets>() {
+            @Override
+            public boolean test(UserAndAllPets userAndAllPets) throws Exception {
+                return userAndAllPets.user.equals(user)
+                        && userAndAllPets.pets.equals(Arrays.asList(pets));
+            }
+        });
+    }
+
+    @Test
+    public void flowable_updateInTransaction() throws InterruptedException {
+        // When subscribing to the emissions of the user
+        final TestSubscriber<User> userTestSubscriber = mUserDao
+                .flowableUserById(3)
+                .observeOn(mTestScheduler)
+                .test();
+        drain();
+        userTestSubscriber.assertValueCount(0);
+
+        // When inserting a new user in the data source
+        mDatabase.beginTransaction();
+        try {
+            mUserDao.insert(TestUtil.createUser(3));
+            mDatabase.setTransactionSuccessful();
+
+        } finally {
+            mDatabase.endTransaction();
+        }
+        drain();
+        userTestSubscriber.assertValueCount(1);
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java b/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
new file mode 100644
index 0000000..fcd0b00
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.arch.core.executor.testing.InstantTaskExecutorRule;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RxJava2WithInstantTaskExecutorTest {
+    @Rule
+    public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
+
+    private TestDatabase mDatabase;
+
+    @Before
+    public void initDb() throws Exception {
+        // using an in-memory database because the information stored here disappears when the
+        // process is killed
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+                TestDatabase.class)
+                // allowing main thread queries, just for testing
+                .allowMainThreadQueries()
+                .build();
+    }
+
+    @Test
+    public void testFlowableInTransaction() {
+        // When subscribing to the emissions of the user
+        TestSubscriber<User> subscriber = mDatabase.getUserDao().flowableUserById(3).test();
+        subscriber.assertValueCount(0);
+
+        // When inserting a new user in the data source
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.getUserDao().insert(TestUtil.createUser(3));
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        subscriber.assertValueCount(1);
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index 8861adb..f8049f3 100644
--- a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -502,4 +502,26 @@
         assertThat(mUserDao.updateByAgeAndIds(3f, 30, Arrays.asList(3, 5)), is(1));
         assertThat(mUserDao.loadByIds(3)[0].getWeight(), is(3f));
     }
+
+    @Test
+    public void transactionByAnnotation() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(5);
+        mUserDao.insertBothByAnnotation(a, b);
+        assertThat(mUserDao.count(), is(2));
+    }
+
+    @Test
+    public void transactionByAnnotation_failure() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(3);
+        boolean caught = false;
+        try {
+            mUserDao.insertBothByAnnotation(a, b);
+        } catch (SQLiteConstraintException e) {
+            caught = true;
+        }
+        assertTrue("SQLiteConstraintException expected", caught);
+        assertThat(mUserDao.count(), is(0));
+    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java b/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
index 51d5bb3..ec77561 100644
--- a/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
@@ -18,6 +18,7 @@
 
 import android.arch.persistence.room.Room;
 import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.FunnyNamedDao;
 import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
 import android.arch.persistence.room.integration.testapp.dao.PetDao;
 import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
@@ -42,6 +43,7 @@
     protected ToyDao mToyDao;
     protected SpecificDogDao mSpecificDogDao;
     protected WithClauseDao mWithClauseDao;
+    protected FunnyNamedDao mFunnyNamedDao;
 
     @Before
     public void createDb() {
@@ -55,5 +57,6 @@
         mToyDao = mDatabase.getToyDao();
         mSpecificDogDao = mDatabase.getSpecificDogDao();
         mWithClauseDao = mDatabase.getWithClauseDao();
+        mFunnyNamedDao = mDatabase.getFunnyNamedDao();
     }
 }
diff --git a/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java b/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
index 10897da..9209638 100644
--- a/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
@@ -20,6 +20,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import android.arch.persistence.room.integration.testapp.vo.User;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -32,6 +34,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class WithClauseTest extends TestDatabaseTest{
     @Test
     public void noSourceOfData() {
diff --git a/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java b/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java
new file mode 100644
index 0000000..20f3c21
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java
@@ -0,0 +1,75 @@
+/*
+ * 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.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+/**
+ * An entity that was weird names
+ */
+@Entity(tableName = FunnyNamedEntity.TABLE_NAME)
+public class FunnyNamedEntity {
+    public static final String TABLE_NAME = "funny but not so funny";
+    public static final String COLUMN_ID = "_this $is id$";
+    public static final String COLUMN_VALUE = "unlikely-Ωşå¨ıünames";
+    @PrimaryKey(autoGenerate = true)
+    @ColumnInfo(name = COLUMN_ID)
+    private int mId;
+    @ColumnInfo(name = COLUMN_VALUE)
+    private String mValue;
+
+    public FunnyNamedEntity(int id, String value) {
+        mId = id;
+        mValue = value;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        mId = id;
+    }
+
+    public String getValue() {
+        return mValue;
+    }
+
+    public void setValue(String value) {
+        mValue = value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FunnyNamedEntity entity = (FunnyNamedEntity) o;
+
+        if (mId != entity.mId) return false;
+        return mValue != null ? mValue.equals(entity.mValue) : entity.mValue == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mValue != null ? mValue.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java b/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
new file mode 100644
index 0000000..cae1843
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class IntegerPKeyEntity {
+    @PrimaryKey
+    public Integer pKey;
+    public String data;
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java b/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
new file mode 100644
index 0000000..895a35a
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.support.annotation.NonNull;
+
+@Entity
+public class ObjectPKeyEntity {
+    @PrimaryKey
+    @NonNull
+    public String pKey;
+    public String data;
+
+    public ObjectPKeyEntity(String pKey, String data) {
+        this.pKey = pKey;
+        this.data = data;
+    }
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/PetCouple.java b/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
index f27b131..e5208ed 100644
--- a/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
+++ b/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
@@ -20,11 +20,13 @@
 import android.arch.persistence.room.Entity;
 import android.arch.persistence.room.PrimaryKey;
 import android.arch.persistence.room.RoomWarnings;
+import android.support.annotation.NonNull;
 
 @Entity
 @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
 public class PetCouple {
     @PrimaryKey
+    @NonNull
     public String id;
     @Embedded(prefix = "male_")
     public Pet male;
diff --git a/android/arch/persistence/room/migration/TableInfoTest.java b/android/arch/persistence/room/migration/TableInfoTest.java
index c6eade5..d88c02f 100644
--- a/android/arch/persistence/room/migration/TableInfoTest.java
+++ b/android/arch/persistence/room/migration/TableInfoTest.java
@@ -37,6 +37,7 @@
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -179,6 +180,35 @@
                 Collections.<TableInfo.ForeignKey>emptySet())));
     }
 
+    @Test
+    public void readIndices() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (n INTEGER, indexed TEXT, unique_indexed TEXT,"
+                        + "a INTEGER, b INTEGER);",
+                "CREATE INDEX foo_indexed ON foo(indexed);",
+                "CREATE UNIQUE INDEX foo_unique_indexed ON foo(unique_indexed COLLATE NOCASE"
+                        + " DESC);",
+                "CREATE INDEX " + TableInfo.Index.DEFAULT_PREFIX + "foo_composite_indexed"
+                        + " ON foo(a, b);"
+        );
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo(
+                "foo",
+                toMap(new TableInfo.Column("n", "INTEGER", false, 0),
+                        new TableInfo.Column("indexed", "TEXT", false, 0),
+                        new TableInfo.Column("unique_indexed", "TEXT", false, 0),
+                        new TableInfo.Column("a", "INTEGER", false, 0),
+                        new TableInfo.Column("b", "INTEGER", false, 0)),
+                Collections.<TableInfo.ForeignKey>emptySet(),
+                toSet(new TableInfo.Index("index_foo_blahblah", false,
+                        Arrays.asList("a", "b")),
+                        new TableInfo.Index("foo_unique_indexed", true,
+                                Arrays.asList("unique_indexed")),
+                        new TableInfo.Index("foo_indexed", false,
+                                Arrays.asList("indexed"))))
+        ));
+    }
+
     private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
         Map<String, TableInfo.Column> result = new HashMap<>();
         for (TableInfo.Column column : columns) {
@@ -187,6 +217,14 @@
         return result;
     }
 
+    private static <T> Set<T> toSet(T... ts) {
+        final HashSet<T> result = new HashSet<T>();
+        for (T t : ts) {
+            result.add(t);
+        }
+        return result;
+    }
+
     @After
     public void closeDb() throws IOException {
         if (mDb != null && mDb.isOpen()) {
@@ -199,8 +237,7 @@
                 SupportSQLiteOpenHelper.Configuration
                         .builder(InstrumentationRegistry.getTargetContext())
                         .name(null)
-                        .version(1)
-                        .callback(new SupportSQLiteOpenHelper.Callback() {
+                        .callback(new SupportSQLiteOpenHelper.Callback(1) {
                             @Override
                             public void onCreate(SupportSQLiteDatabase db) {
                                 for (String query : queries) {
diff --git a/android/arch/persistence/room/package-info.java b/android/arch/persistence/room/package-info.java
index faaa952..1dafc1b 100644
--- a/android/arch/persistence/room/package-info.java
+++ b/android/arch/persistence/room/package-info.java
@@ -39,8 +39,8 @@
  *     database row. For each {@link android.arch.persistence.room.Entity Entity}, a database table
  *     is created to hold the items. The Entity class must be referenced in the
  *     {@link android.arch.persistence.room.Database#entities() Database#entities} array. Each field
- *     of the Entity is persisted in the database unless it is annotated with
- *     {@link android.arch.persistence.room.Ignore Ignore}. Entities must have no-arg constructors.
+ *     of the Entity (and its super class) is persisted in the database unless it is denoted
+ *     otherwise (see {@link android.arch.persistence.room.Entity Entity} docs for details).
  *     </li>
  *     <li>{@link android.arch.persistence.room.Dao Dao}: This annotation marks a class or interface
  *     as a Data Access Object. Data access objects are the main component of Room that are
diff --git a/android/arch/persistence/room/testing/MigrationTestHelper.java b/android/arch/persistence/room/testing/MigrationTestHelper.java
index aea3e96..18e0a14 100644
--- a/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ b/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -29,6 +29,7 @@
 import android.arch.persistence.room.migration.bundle.EntityBundle;
 import android.arch.persistence.room.migration.bundle.FieldBundle;
 import android.arch.persistence.room.migration.bundle.ForeignKeyBundle;
+import android.arch.persistence.room.migration.bundle.IndexBundle;
 import android.arch.persistence.room.migration.bundle.SchemaBundle;
 import android.arch.persistence.room.util.TableInfo;
 import android.content.Context;
@@ -146,7 +147,7 @@
         RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
                 new CreatingDelegate(schemaBundle.getDatabase()),
                 schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, version, roomOpenHelper);
+        return openDatabase(name, roomOpenHelper);
     }
 
     /**
@@ -189,17 +190,15 @@
         RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
                 new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),
                 schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, version, roomOpenHelper);
+        return openDatabase(name, roomOpenHelper);
     }
 
-    private SupportSQLiteDatabase openDatabase(String name, int version,
-            RoomOpenHelper roomOpenHelper) {
+    private SupportSQLiteDatabase openDatabase(String name, RoomOpenHelper roomOpenHelper) {
         SupportSQLiteOpenHelper.Configuration config =
                 SupportSQLiteOpenHelper.Configuration
                         .builder(mInstrumentation.getTargetContext())
                         .callback(roomOpenHelper)
                         .name(name)
-                        .version(version)
                         .build();
         SupportSQLiteDatabase db = mOpenFactory.create(config).getWritableDatabase();
         mManagedDatabases.add(new WeakReference<>(db));
@@ -287,7 +286,19 @@
 
     private static TableInfo toTableInfo(EntityBundle entityBundle) {
         return new TableInfo(entityBundle.getTableName(), toColumnMap(entityBundle),
-                toForeignKeys(entityBundle.getForeignKeys()));
+                toForeignKeys(entityBundle.getForeignKeys()), toIndices(entityBundle.getIndices()));
+    }
+
+    private static Set<TableInfo.Index> toIndices(List<IndexBundle> indices) {
+        if (indices == null) {
+            return Collections.emptySet();
+        }
+        Set<TableInfo.Index> result = new HashSet<>();
+        for (IndexBundle bundle : indices) {
+            result.add(new TableInfo.Index(bundle.getName(), bundle.isUnique(),
+                    bundle.getColumnNames()));
+        }
+        return result;
     }
 
     private static Set<TableInfo.ForeignKey> toForeignKeys(
@@ -401,6 +412,7 @@
         final DatabaseBundle mDatabaseBundle;
 
         RoomOpenHelperDelegate(DatabaseBundle databaseBundle) {
+            super(databaseBundle.getVersion());
             mDatabaseBundle = databaseBundle;
         }
 
diff --git a/android/arch/persistence/room/util/TableInfo.java b/android/arch/persistence/room/util/TableInfo.java
index bcd2e9e..a115147 100644
--- a/android/arch/persistence/room/util/TableInfo.java
+++ b/android/arch/persistence/room/util/TableInfo.java
@@ -20,6 +20,7 @@
 import android.database.Cursor;
 import android.os.Build;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 
 import java.util.ArrayList;
@@ -29,6 +30,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 /**
  * A data class that holds the information about a table.
@@ -56,11 +58,70 @@
 
     public final Set<ForeignKey> foreignKeys;
 
+    /**
+     * Sometimes, Index information is not available (older versions). If so, we skip their
+     * verification.
+     */
+    @Nullable
+    public final Set<Index> indices;
+
     @SuppressWarnings("unused")
-    public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
+    public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys,
+            Set<Index> indices) {
         this.name = name;
         this.columns = Collections.unmodifiableMap(columns);
         this.foreignKeys = Collections.unmodifiableSet(foreignKeys);
+        this.indices = indices == null ? null : Collections.unmodifiableSet(indices);
+    }
+
+    /**
+     * For backward compatibility with dbs created with older versions.
+     */
+    @SuppressWarnings("unused")
+    public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
+        this(name, columns, foreignKeys, Collections.<Index>emptySet());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        TableInfo tableInfo = (TableInfo) o;
+
+        if (name != null ? !name.equals(tableInfo.name) : tableInfo.name != null) return false;
+        if (columns != null ? !columns.equals(tableInfo.columns) : tableInfo.columns != null) {
+            return false;
+        }
+        if (foreignKeys != null ? !foreignKeys.equals(tableInfo.foreignKeys)
+                : tableInfo.foreignKeys != null) {
+            return false;
+        }
+        if (indices == null || tableInfo.indices == null) {
+            // if one us is missing index information, seems like we couldn't acquire the
+            // information so we better skip.
+            return true;
+        }
+        return indices.equals(tableInfo.indices);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (columns != null ? columns.hashCode() : 0);
+        result = 31 * result + (foreignKeys != null ? foreignKeys.hashCode() : 0);
+        // skip index, it is not reliable for comparison.
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "TableInfo{"
+                + "name='" + name + '\''
+                + ", columns=" + columns
+                + ", foreignKeys=" + foreignKeys
+                + ", indices=" + indices
+                + '}';
     }
 
     /**
@@ -74,7 +135,8 @@
     public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
         Map<String, Column> columns = readColumns(database, tableName);
         Set<ForeignKey> foreignKeys = readForeignKeys(database, tableName);
-        return new TableInfo(tableName, columns, foreignKeys);
+        Set<Index> indices = readIndices(database, tableName);
+        return new TableInfo(tableName, columns, foreignKeys, indices);
     }
 
     private static Set<ForeignKey> readForeignKeys(SupportSQLiteDatabase database,
@@ -167,34 +229,74 @@
         return columns;
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        TableInfo tableInfo = (TableInfo) o;
-
-        if (!name.equals(tableInfo.name)) return false;
-        //noinspection SimplifiableIfStatement
-        if (!columns.equals(tableInfo.columns)) return false;
-        return foreignKeys.equals(tableInfo.foreignKeys);
+    /**
+     * @return null if we cannot read the indices due to older sqlite implementations.
+     */
+    @Nullable
+    private static Set<Index> readIndices(SupportSQLiteDatabase database, String tableName) {
+        Cursor cursor = database.query("PRAGMA index_list(`" + tableName + "`)");
+        try {
+            final int nameColumnIndex = cursor.getColumnIndex("name");
+            final int originColumnIndex = cursor.getColumnIndex("origin");
+            final int uniqueIndex = cursor.getColumnIndex("unique");
+            if (nameColumnIndex == -1 || originColumnIndex == -1 || uniqueIndex == -1) {
+                // we cannot read them so better not validate any index.
+                return null;
+            }
+            HashSet<Index> indices = new HashSet<>();
+            while (cursor.moveToNext()) {
+                String origin = cursor.getString(originColumnIndex);
+                if (!"c".equals(origin)) {
+                    // Ignore auto-created indices
+                    continue;
+                }
+                String name = cursor.getString(nameColumnIndex);
+                boolean unique = cursor.getInt(uniqueIndex) == 1;
+                Index index = readIndex(database, name, unique);
+                if (index == null) {
+                    // we cannot read it properly so better not read it
+                    return null;
+                }
+                indices.add(index);
+            }
+            return indices;
+        } finally {
+            cursor.close();
+        }
     }
 
-    @Override
-    public int hashCode() {
-        int result = name.hashCode();
-        result = 31 * result + columns.hashCode();
-        result = 31 * result + foreignKeys.hashCode();
-        return result;
-    }
+    /**
+     * @return null if we cannot read the index due to older sqlite implementations.
+     */
+    @Nullable
+    private static Index readIndex(SupportSQLiteDatabase database, String name, boolean unique) {
+        Cursor cursor = database.query("PRAGMA index_xinfo(`" + name + "`)");
+        try {
+            final int seqnoColumnIndex = cursor.getColumnIndex("seqno");
+            final int cidColumnIndex = cursor.getColumnIndex("cid");
+            final int nameColumnIndex = cursor.getColumnIndex("name");
+            if (seqnoColumnIndex == -1 || cidColumnIndex == -1 || nameColumnIndex == -1) {
+                // we cannot read them so better not validate any index.
+                return null;
+            }
+            final TreeMap<Integer, String> results = new TreeMap<>();
 
-    @Override
-    public String toString() {
-        return "TableInfo{"
-                + "name='" + name + '\''
-                + ", columns=" + columns
-                + ", foreignKeys=" + foreignKeys
-                + '}';
+            while (cursor.moveToNext()) {
+                int cid = cursor.getInt(cidColumnIndex);
+                if (cid < 0) {
+                    // Ignore SQLite row ID
+                    continue;
+                }
+                int seq = cursor.getInt(seqnoColumnIndex);
+                String columnName = cursor.getString(nameColumnIndex);
+                results.put(seq, columnName);
+            }
+            final List<String> columns = new ArrayList<>(results.size());
+            columns.addAll(results.values());
+            return new Index(name, unique, columns);
+        } finally {
+            cursor.close();
+        }
     }
 
     /**
@@ -379,4 +481,65 @@
             }
         }
     }
+
+    /**
+     * Holds the information about an SQLite index
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static class Index {
+        // should match the value in Index.kt
+        public static final String DEFAULT_PREFIX = "index_";
+        public final String name;
+        public final boolean unique;
+        public final List<String> columns;
+
+        public Index(String name, boolean unique, List<String> columns) {
+            this.name = name;
+            this.unique = unique;
+            this.columns = columns;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Index index = (Index) o;
+            if (unique != index.unique) {
+                return false;
+            }
+            if (!columns.equals(index.columns)) {
+                return false;
+            }
+            if (name.startsWith(Index.DEFAULT_PREFIX)) {
+                return index.name.startsWith(Index.DEFAULT_PREFIX);
+            } else {
+                return name.equals(index.name);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            int result;
+            if (name.startsWith(DEFAULT_PREFIX)) {
+                result = DEFAULT_PREFIX.hashCode();
+            } else {
+                result = name.hashCode();
+            }
+            result = 31 * result + (unique ? 1 : 0);
+            result = 31 * result + columns.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Index{"
+                    + "name='" + name + '\''
+                    + ", unique=" + unique
+                    + ", columns=" + columns
+                    + '}';
+        }
+    }
 }
diff --git a/android/bluetooth/BluetoothAdapter.java b/android/bluetooth/BluetoothAdapter.java
index 70591d4..84765f6 100644
--- a/android/bluetooth/BluetoothAdapter.java
+++ b/android/bluetooth/BluetoothAdapter.java
@@ -1134,6 +1134,29 @@
     }
 
     /**
+     * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of
+     * the local Bluetooth adapter.
+     *
+     * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to.
+     * @return true if successful, false if unsuccessful.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+        if (getState() != STATE_ON) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.setBluetoothClass(bluetoothClass);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
      * Get the current Bluetooth scan mode of the local Bluetooth adapter.
      * <p>The Bluetooth scan mode determines if the local adapter is
      * connectable and/or discoverable from remote Bluetooth devices.
diff --git a/android/bluetooth/BluetoothClass.java b/android/bluetooth/BluetoothClass.java
index 57e4abb..f22ea6e 100644
--- a/android/bluetooth/BluetoothClass.java
+++ b/android/bluetooth/BluetoothClass.java
@@ -19,6 +19,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
 /**
  * Represents a Bluetooth class, which describes general characteristics
  * and capabilities of a device. For example, a Bluetooth class will
@@ -275,6 +279,48 @@
         return (mClass & Device.BITMASK);
     }
 
+    /**
+     * Return the Bluetooth Class of Device (CoD) value including the
+     * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+     * minor device fields.
+     *
+     * <p>This value is an integer representation of Bluetooth CoD as in
+     * Bluetooth specification.
+     *
+     * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+     *
+     * @hide
+     */
+    public int getClassOfDevice() {
+        return mClass;
+    }
+
+    /**
+     * Return the Bluetooth Class of Device (CoD) value including the
+     * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+     * minor device fields.
+     *
+     * <p>This value is a byte array representation of Bluetooth CoD as in
+     * Bluetooth specification.
+     *
+     * <p>Bluetooth COD information is 3 bytes, but stored as an int. Hence the
+     * MSB is useless and needs to be thrown away. The lower 3 bytes are
+     * converted into a byte array MSB to LSB. Hence, using BIG_ENDIAN.
+     *
+     * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+     *
+     * @hide
+     */
+    public byte[] getClassOfDeviceBytes() {
+        byte[] bytes = ByteBuffer.allocate(4)
+                .order(ByteOrder.BIG_ENDIAN)
+                .putInt(mClass)
+                .array();
+
+        // Discard the top byte
+        return Arrays.copyOfRange(bytes, 1, bytes.length);
+    }
+
     /** @hide */
     public static final int PROFILE_HEADSET = 0;
     /** @hide */
diff --git a/android/bluetooth/BluetoothInputHost.java b/android/bluetooth/BluetoothInputHost.java
index 37f0427..e18d9d1 100644
--- a/android/bluetooth/BluetoothInputHost.java
+++ b/android/bluetooth/BluetoothInputHost.java
@@ -74,7 +74,7 @@
     public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
     public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
     public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
-    public static final byte SUBCLASS2_DIGITIZER_TABLED = (byte) 0x05;
+    public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05;
     public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
 
     /**
diff --git a/android/bluetooth/BluetoothPbap.java b/android/bluetooth/BluetoothPbap.java
index 19f5198..a1a9347 100644
--- a/android/bluetooth/BluetoothPbap.java
+++ b/android/bluetooth/BluetoothPbap.java
@@ -16,6 +16,7 @@
 
 package android.bluetooth;
 
+import android.annotation.SdkConstant;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -53,35 +54,32 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
-    /** int extra for PBAP_STATE_CHANGED_ACTION */
-    public static final String PBAP_STATE =
-            "android.bluetooth.pbap.intent.PBAP_STATE";
-    /** int extra for PBAP_STATE_CHANGED_ACTION */
-    public static final String PBAP_PREVIOUS_STATE =
-            "android.bluetooth.pbap.intent.PBAP_PREVIOUS_STATE";
-
     /**
-     * Indicates the state of a pbap connection state has changed.
-     * This intent will always contain PBAP_STATE, PBAP_PREVIOUS_STATE and
-     * BluetoothIntent.ADDRESS extras.
+     * Intent used to broadcast the change in connection state of the PBAP
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
+     *  can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
+     *  {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
+     *  {@link BluetoothProfile#STATE_DISCONNECTING}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
      */
-    public static final String PBAP_STATE_CHANGED_ACTION =
-            "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED";
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
 
     private volatile IBluetoothPbap mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
 
-    /** There was an error trying to obtain the state */
-    public static final int STATE_ERROR = -1;
-    /** No client currently connected */
-    public static final int STATE_DISCONNECTED = 0;
-    /** Connection attempt in progress */
-    public static final int STATE_CONNECTING = 1;
-    /** Client is currently connected */
-    public static final int STATE_CONNECTED = 2;
-
     public static final int RESULT_FAILURE = 0;
     public static final int RESULT_SUCCESS = 1;
     /** Connection canceled before completion. */
@@ -209,8 +207,8 @@
     /**
      * Get the current state of the BluetoothPbap service.
      *
-     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
-     * connected to the Pbap service.
+     * @return One of the STATE_ return codes, or {@link BluetoothProfile#STATE_DISCONNECTED}
+     * if this proxy object is currently not connected to the Pbap service.
      */
     public int getState() {
         if (VDBG) log("getState()");
@@ -225,7 +223,7 @@
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
         }
-        return BluetoothPbap.STATE_ERROR;
+        return BluetoothProfile.STATE_DISCONNECTED;
     }
 
     /**
diff --git a/android/bluetooth/BluetoothPbapClient.java b/android/bluetooth/BluetoothPbapClient.java
index 00a15f3..01b3f6e 100644
--- a/android/bluetooth/BluetoothPbapClient.java
+++ b/android/bluetooth/BluetoothPbapClient.java
@@ -40,7 +40,7 @@
     private static final boolean VDBG = false;
 
     public static final String ACTION_CONNECTION_STATE_CHANGED =
-            "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+            "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED";
 
     private volatile IBluetoothPbapClient mService;
     private final Context mContext;
diff --git a/android/bluetooth/BluetoothUuid.java b/android/bluetooth/BluetoothUuid.java
index 5bfc54d..76cb3f5 100644
--- a/android/bluetooth/BluetoothUuid.java
+++ b/android/bluetooth/BluetoothUuid.java
@@ -232,7 +232,7 @@
      */
     public static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) {
         UUID uuid = parcelUuid.getUuid();
-        long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32;
+        long value = (uuid.getMostSignificantBits() & 0xFFFFFFFF00000000L) >>> 32;
         return (int) value;
     }
 
diff --git a/android/content/ComponentName.java b/android/content/ComponentName.java
index ea6b769..0d36bdd 100644
--- a/android/content/ComponentName.java
+++ b/android/content/ComponentName.java
@@ -21,9 +21,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
-import java.lang.Comparable;
 
 /**
  * Identifier for a specific application component
@@ -33,7 +33,7 @@
  * pieces of information, encapsulated here, are required to identify
  * a component: the package (a String) it exists in, and the class (a String)
  * name inside of that package.
- * 
+ *
  */
 public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
     private final String mPackage;
@@ -91,7 +91,7 @@
 
     /**
      * Create a new component identifier.
-     * 
+     *
      * @param pkg The name of the package that the component exists in.  Can
      * not be null.
      * @param cls The name of the class inside of <var>pkg</var> that
@@ -106,7 +106,7 @@
 
     /**
      * Create a new component identifier from a Context and class name.
-     * 
+     *
      * @param pkg A Context for the package implementing the component,
      * from which the actual package name will be retrieved.
      * @param cls The name of the class inside of <var>pkg</var> that
@@ -120,7 +120,7 @@
 
     /**
      * Create a new component identifier from a Context and Class object.
-     * 
+     *
      * @param pkg A Context for the package implementing the component, from
      * which the actual package name will be retrieved.
      * @param cls The Class object of the desired component, from which the
@@ -141,14 +141,14 @@
     public @NonNull String getPackageName() {
         return mPackage;
     }
-    
+
     /**
      * Return the class name of this component.
      */
     public @NonNull String getClassName() {
         return mClass;
     }
-    
+
     /**
      * Return the class name, either fully qualified or in a shortened form
      * (with a leading '.') if it is a suffix of the package.
@@ -163,7 +163,7 @@
         }
         return mClass;
     }
-    
+
     private static void appendShortClassName(StringBuilder sb, String packageName,
             String className) {
         if (className.startsWith(packageName)) {
@@ -195,26 +195,26 @@
      * class names contained in the ComponentName.  You can later recover
      * the ComponentName from this string through
      * {@link #unflattenFromString(String)}.
-     * 
+     *
      * @return Returns a new String holding the package and class names.  This
      * is represented as the package name, concatenated with a '/' and then the
      * class name.
-     * 
+     *
      * @see #unflattenFromString(String)
      */
     public @NonNull String flattenToString() {
         return mPackage + "/" + mClass;
     }
-    
+
     /**
      * The same as {@link #flattenToString()}, but abbreviates the class
      * name if it is a suffix of the package.  The result can still be used
      * with {@link #unflattenFromString(String)}.
-     * 
+     *
      * @return Returns a new String holding the package and class names.  This
      * is represented as the package name, concatenated with a '/' and then the
      * class name.
-     * 
+     *
      * @see #unflattenFromString(String)
      */
     public @NonNull String flattenToShortString() {
@@ -250,11 +250,11 @@
      * followed by a '.' then the final class name will be the concatenation
      * of the package name with the string following the '/'.  Thus
      * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
-     * 
+     *
      * @param str The String that was returned by flattenToString().
      * @return Returns a new ComponentName containing the package and class
      * names that were encoded in <var>str</var>
-     * 
+     *
      * @see #flattenToString()
      */
     public static @Nullable ComponentName unflattenFromString(@NonNull String str) {
@@ -269,7 +269,7 @@
         }
         return new ComponentName(pkg, cls);
     }
-    
+
     /**
      * Return string representation of this class without the class's name
      * as a prefix.
@@ -283,6 +283,12 @@
         return "ComponentInfo{" + mPackage + "/" + mClass + "}";
     }
 
+    /** Put this here so that individual services don't have to reimplement this. @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
+        proto.write(ComponentNameProto.CLASS_NAME, mClass);
+    }
+
     @Override
     public boolean equals(Object obj) {
         try {
@@ -311,7 +317,7 @@
         }
         return this.mClass.compareTo(that.mClass);
     }
-    
+
     public int describeContents() {
         return 0;
     }
@@ -324,10 +330,10 @@
     /**
      * Write a ComponentName to a Parcel, handling null pointers.  Must be
      * read with {@link #readFromParcel(Parcel)}.
-     * 
+     *
      * @param c The ComponentName to be written.
      * @param out The Parcel in which the ComponentName will be placed.
-     * 
+     *
      * @see #readFromParcel(Parcel)
      */
     public static void writeToParcel(ComponentName c, Parcel out) {
@@ -337,23 +343,23 @@
             out.writeString(null);
         }
     }
-    
+
     /**
      * Read a ComponentName from a Parcel that was previously written
      * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
      * a null or new object as appropriate.
-     * 
+     *
      * @param in The Parcel from which to read the ComponentName
      * @return Returns a new ComponentName matching the previously written
      * object, or null if a null had been written.
-     * 
+     *
      * @see #writeToParcel(ComponentName, Parcel)
      */
     public static ComponentName readFromParcel(Parcel in) {
         String pkg = in.readString();
         return pkg != null ? new ComponentName(pkg, in) : null;
     }
-    
+
     public static final Parcelable.Creator<ComponentName> CREATOR
             = new Parcelable.Creator<ComponentName>() {
         public ComponentName createFromParcel(Parcel in) {
@@ -371,7 +377,7 @@
      * must not use this with data written by
      * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
      * to handle a null ComponentObject here.
-     * 
+     *
      * @param in The Parcel containing the previously written ComponentName,
      * positioned at the location in the buffer where it was written.
      */
diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java
index cdeaea3..5b2bf45 100644
--- a/android/content/ContentProvider.java
+++ b/android/content/ContentProvider.java
@@ -2099,7 +2099,8 @@
     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_CONTENT.equals(uri.getScheme())
+                        || ContentResolver.SCHEME_SLICE.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 9ccc552..02e70f5 100644
--- a/android/content/ContentResolver.java
+++ b/android/content/ContentResolver.java
@@ -47,6 +47,8 @@
 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;
@@ -178,6 +180,8 @@
     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";
@@ -1718,6 +1722,36 @@
     }
 
     /**
+     * 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
@@ -1725,7 +1759,7 @@
      * @hide
      */
     public final IContentProvider acquireProvider(Uri uri) {
-        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
+        if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) {
             return null;
         }
         final String auth = uri.getAuthority();
diff --git a/android/content/Context.java b/android/content/Context.java
index 2d8249a..20fbf04 100644
--- a/android/content/Context.java
+++ b/android/content/Context.java
@@ -64,6 +64,7 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.WindowManager;
+import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.textclassifier.TextClassificationManager;
 
 import java.io.File;
@@ -2991,6 +2992,7 @@
             //@hide: CONTEXTHUB_SERVICE,
             SYSTEM_HEALTH_SERVICE,
             //@hide: INCIDENT_SERVICE,
+            //@hide: STATS_COMPANION_SERVICE,
             COMPANION_DEVICE_SERVICE
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -3033,6 +3035,9 @@
      *  <dt> {@link #CONNECTIVITY_SERVICE} ("connection")
      *  <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
      *  handling management of network connections.
+     *  <dt> {@link #IPSEC_SERVICE} ("ipsec")
+     *  <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on
+     *  sockets and networks.
      *  <dt> {@link #WIFI_SERVICE} ("wifi")
      *  <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
      *  connectivity.  On releases before NYC, it should only be obtained from an application
@@ -3377,7 +3382,6 @@
      * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
      * IPSec.
      *
-     * @hide
      * @see #getSystemService
      */
     public static final String IPSEC_SERVICE = "ipsec";
@@ -3464,6 +3468,19 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.wifi.rtt.WifiRttManager} for ranging devices with wifi
+     *
+     * Note: this is a replacement for WIFI_RTT_SERVICE above. It will
+     * be renamed once final implementation in place.
+     *
+     * @see #getSystemService
+     * @see android.net.wifi.rtt.WifiRttManager
+     * @hide
+     */
+    public static final String WIFI_RTT2_SERVICE = "rttmanager2";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.lowpan.LowpanManager} for handling management of
      * LoWPAN access.
      *
@@ -4020,6 +4037,12 @@
     public static final String INCIDENT_SERVICE = "incident";
 
     /**
+     * Service to assist statsd in obtaining general stats.
+     * @hide
+     */
+    public static final String STATS_COMPANION_SERVICE = "statscompanion";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.content.om.OverlayManager} for managing overlay packages.
      *
@@ -4765,6 +4788,19 @@
     }
 
     /**
+     * @hide
+     */
+    public AutofillClient getAutofillClient() {
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAutofillClient(AutofillClient client) {
+    }
+
+    /**
      * Throws an exception if the Context is using system resources,
      * which are non-runtime-overlay-themable and may show inconsistent UI.
      * @hide
diff --git a/android/content/ContextWrapper.java b/android/content/ContextWrapper.java
index a9fd58b..85acdc6 100644
--- a/android/content/ContextWrapper.java
+++ b/android/content/ContextWrapper.java
@@ -37,6 +37,7 @@
 import android.os.UserHandle;
 import android.view.Display;
 import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -967,7 +968,24 @@
     /**
      * @hide
      */
+    @Override
     public int getNextAutofillId() {
         return mBase.getNextAutofillId();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public AutofillClient getAutofillClient() {
+        return mBase.getAutofillClient();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void setAutofillClient(AutofillClient client) {
+        mBase.setAutofillClient(client);
+    }
 }
diff --git a/android/content/Intent.java b/android/content/Intent.java
index 08acfb6..c9ad951 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -9444,7 +9444,7 @@
                 for (int i=0; i<N; i++) {
                     char c = data.charAt(i);
                     if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
-                            || c == '.' || c == '-') {
+                            || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+') {
                         continue;
                     }
                     if (c == ':' && i > 0) {
@@ -10071,6 +10071,27 @@
         return false;
     }
 
+    /**
+     * Convert the dock state to a human readable format.
+     * @hide
+     */
+    public static String dockStateToString(int dock) {
+        switch (dock) {
+            case EXTRA_DOCK_STATE_HE_DESK:
+                return "EXTRA_DOCK_STATE_HE_DESK";
+            case EXTRA_DOCK_STATE_LE_DESK:
+                return "EXTRA_DOCK_STATE_LE_DESK";
+            case EXTRA_DOCK_STATE_CAR:
+                return "EXTRA_DOCK_STATE_CAR";
+            case EXTRA_DOCK_STATE_DESK:
+                return "EXTRA_DOCK_STATE_DESK";
+            case EXTRA_DOCK_STATE_UNDOCKED:
+                return "EXTRA_DOCK_STATE_UNDOCKED";
+            default:
+                return Integer.toString(dock);
+        }
+    }
+
     private static ClipData.Item makeClipItem(ArrayList<Uri> streams, ArrayList<CharSequence> texts,
             ArrayList<String> htmlTexts, int which) {
         Uri uri = streams != null ? streams.get(which) : null;
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
index 48587b3..41667c4 100644
--- a/android/content/pm/ActivityInfo.java
+++ b/android/content/pm/ActivityInfo.java
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import android.annotation.IntDef;
+import android.annotation.TestApi;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Configuration.NativeConfig;
@@ -34,8 +35,7 @@
  * from the AndroidManifest.xml's &lt;activity&gt; and
  * &lt;receiver&gt; tags.
  */
-public class ActivityInfo extends ComponentInfo
-        implements Parcelable {
+public class ActivityInfo extends ComponentInfo implements Parcelable {
 
      // NOTE: When adding new data members be sure to update the copy-constructor, Parcel
      // constructor, and writeToParcel.
@@ -181,6 +181,7 @@
      * Activity explicitly requested to be resizeable.
      * @hide
      */
+    @TestApi
     public static final int RESIZE_MODE_RESIZEABLE = 2;
     /**
      * Activity is resizeable and supported picture-in-picture mode.  This flag is now deprecated
@@ -1211,6 +1212,67 @@
         return isFloating || isTranslucent || isSwipeToDismiss;
     }
 
+    /**
+     * Convert the screen orientation constant to a human readable format.
+     * @hide
+     */
+    public static String screenOrientationToString(int orientation) {
+        switch (orientation) {
+            case SCREEN_ORIENTATION_UNSET:
+                return "SCREEN_ORIENTATION_UNSET";
+            case SCREEN_ORIENTATION_UNSPECIFIED:
+                return "SCREEN_ORIENTATION_UNSPECIFIED";
+            case SCREEN_ORIENTATION_LANDSCAPE:
+                return "SCREEN_ORIENTATION_LANDSCAPE";
+            case SCREEN_ORIENTATION_PORTRAIT:
+                return "SCREEN_ORIENTATION_PORTRAIT";
+            case SCREEN_ORIENTATION_USER:
+                return "SCREEN_ORIENTATION_USER";
+            case SCREEN_ORIENTATION_BEHIND:
+                return "SCREEN_ORIENTATION_BEHIND";
+            case SCREEN_ORIENTATION_SENSOR:
+                return "SCREEN_ORIENTATION_SENSOR";
+            case SCREEN_ORIENTATION_NOSENSOR:
+                return "SCREEN_ORIENTATION_NOSENSOR";
+            case SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+                return "SCREEN_ORIENTATION_SENSOR_LANDSCAPE";
+            case SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+                return "SCREEN_ORIENTATION_SENSOR_PORTRAIT";
+            case SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+                return "SCREEN_ORIENTATION_REVERSE_LANDSCAPE";
+            case SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+                return "SCREEN_ORIENTATION_REVERSE_PORTRAIT";
+            case SCREEN_ORIENTATION_FULL_SENSOR:
+                return "SCREEN_ORIENTATION_FULL_SENSOR";
+            case SCREEN_ORIENTATION_USER_LANDSCAPE:
+                return "SCREEN_ORIENTATION_USER_LANDSCAPE";
+            case SCREEN_ORIENTATION_USER_PORTRAIT:
+                return "SCREEN_ORIENTATION_USER_PORTRAIT";
+            case SCREEN_ORIENTATION_FULL_USER:
+                return "SCREEN_ORIENTATION_FULL_USER";
+            case SCREEN_ORIENTATION_LOCKED:
+                return "SCREEN_ORIENTATION_LOCKED";
+            default:
+                return Integer.toString(orientation);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static String colorModeToString(@ColorMode int colorMode) {
+        switch (colorMode) {
+            case COLOR_MODE_DEFAULT:
+                return "COLOR_MODE_DEFAULT";
+            case COLOR_MODE_WIDE_COLOR_GAMUT:
+                return "COLOR_MODE_WIDE_COLOR_GAMUT";
+            case COLOR_MODE_HDR:
+                return "COLOR_MODE_HDR";
+            default:
+                return Integer.toString(colorMode);
+        }
+    }
+
     public static final Parcelable.Creator<ActivityInfo> CREATOR
             = new Parcelable.Creator<ActivityInfo>() {
         public ActivityInfo createFromParcel(Parcel source) {
diff --git a/android/content/pm/PackageManager.java b/android/content/pm/PackageManager.java
index ef8f84b..31ca198 100644
--- a/android/content/pm/PackageManager.java
+++ b/android/content/pm/PackageManager.java
@@ -2330,6 +2330,16 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Wi-Fi RTT (IEEE 802.11mc).
+     *
+     * @hide RTT_API
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports LoWPAN networking.
      * @hide
      */
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index 4c981cd..be7f921 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -16,6 +16,9 @@
 
 package android.content.pm;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
@@ -25,6 +28,8 @@
 import android.os.Bundle;
 import android.util.SparseArray;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -33,6 +38,20 @@
  * @hide Only for use within the system server.
  */
 public abstract class PackageManagerInternal {
+    public static final int PACKAGE_SYSTEM = 0;
+    public static final int PACKAGE_SETUP_WIZARD = 1;
+    public static final int PACKAGE_INSTALLER = 2;
+    public static final int PACKAGE_VERIFIER = 3;
+    public static final int PACKAGE_BROWSER = 4;
+    @IntDef(value = {
+        PACKAGE_SYSTEM,
+        PACKAGE_SETUP_WIZARD,
+        PACKAGE_INSTALLER,
+        PACKAGE_VERIFIER,
+        PACKAGE_BROWSER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface KnownPackage {}
 
     /**
      * Provider for package names.
@@ -172,6 +191,13 @@
             @ResolveInfoFlags int flags, int filterCallingUid, int userId);
 
     /**
+     * Retrieve all services that can be performed for the given intent.
+     * @see PackageManager#queryIntentServices(Intent, int)
+     */
+    public abstract List<ResolveInfo> queryIntentServices(
+            Intent intent, int flags, int callingUid, int userId);
+
+    /**
      * Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}.
      */
     public abstract ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
@@ -343,14 +369,19 @@
      * Resolves an activity intent, allowing instant apps to be resolved.
      */
     public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType,
-            int flags, int userId);
+            int flags, int userId, boolean resolveForStart);
 
     /**
     * Resolves a service intent, allowing instant apps to be resolved.
     */
-   public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
+    public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
            int flags, int userId, int callingUid);
 
+   /**
+    * Resolves a content provider intent.
+    */
+    public abstract ProviderInfo resolveContentProvider(String name, int flags, int userId);
+
     /**
      * Track the creator of a new isolated uid.
      * @param isolatedUid The newly created isolated uid.
@@ -383,4 +414,59 @@
      * Updates a package last used time.
      */
     public abstract void notifyPackageUse(String packageName, int reason);
+
+    /**
+     * Returns a package object for the given package name.
+     */
+    public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
+
+    /**
+     * Returns a package object for the disabled system package name.
+     */
+    public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName);
+
+    /**
+     * Returns whether or not the component is the resolver activity.
+     */
+    public abstract boolean isResolveActivityComponent(@NonNull ComponentInfo component);
+
+    /**
+     * Returns the package name for a known package.
+     */
+    public abstract @Nullable String getKnownPackageName(
+            @KnownPackage int knownPackage, int userId);
+
+    /**
+     * Returns whether the package is an instant app.
+     */
+    public abstract boolean isInstantApp(String packageName, int userId);
+
+    /**
+     * Returns whether the package is an instant app.
+     */
+    public abstract @Nullable String getInstantAppPackageName(int uid);
+
+    /**
+     * Returns whether or not access to the application should be filtered.
+     * <p>
+     * Access may be limited based upon whether the calling or target applications
+     * are instant applications.
+     *
+     * @see #canAccessInstantApps(int)
+     */
+    public abstract boolean filterAppAccess(
+            @Nullable PackageParser.Package pkg, int callingUid, int userId);
+
+    /*
+     * NOTE: The following methods are temporary until permissions are extracted from
+     * the package manager into a component specifically for handling permissions.
+     */
+    /** Returns the flags for the given permission. */
+    public abstract @Nullable int getPermissionFlagsTEMP(@NonNull String permName,
+            @NonNull String packageName, int userId);
+    /** 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);
 }
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index 17b4f87..b45c26c 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -318,16 +318,19 @@
         return null;
     }
 
+    @Override
     public String toString() {
         return "PermissionInfo{"
             + Integer.toHexString(System.identityHashCode(this))
             + " " + name + "}";
     }
 
+    @Override
     public int describeContents() {
         return 0;
     }
 
+    @Override
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         super.writeToParcel(dest, parcelableFlags);
         dest.writeInt(protectionLevel);
@@ -338,11 +341,25 @@
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
     }
 
+    /** @hide */
+    public int calculateFootprint() {
+        int size = name.length();
+        if (nonLocalizedLabel != null) {
+            size += nonLocalizedLabel.length();
+        }
+        if (nonLocalizedDescription != null) {
+            size += nonLocalizedDescription.length();
+        }
+        return size;
+    }
+
     public static final Creator<PermissionInfo> CREATOR =
         new Creator<PermissionInfo>() {
+        @Override
         public PermissionInfo createFromParcel(Parcel source) {
             return new PermissionInfo(source);
         }
+        @Override
         public PermissionInfo[] newArray(int size) {
             return new PermissionInfo[size];
         }
diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java
index d3a3560..6b9c753 100644
--- a/android/content/pm/ShortcutInfo.java
+++ b/android/content/pm/ShortcutInfo.java
@@ -1763,21 +1763,43 @@
         return 0;
     }
 
+
     /**
      * Return a string representation, intended for logging.  Some fields will be retracted.
      */
     @Override
     public String toString() {
-        return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
+        return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false,
+                /*indent=*/ null);
     }
 
     /** @hide */
     public String toInsecureString() {
-        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
+        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true,
+                /*indent=*/ null);
     }
 
-    private String toStringInner(boolean secure, boolean includeInternalData) {
+    /** @hide */
+    public String toDumpString(String indent) {
+        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
+    }
+
+    private void addIndentOrComma(StringBuilder sb, String indent) {
+        if (indent != null) {
+            sb.append("\n  ");
+            sb.append(indent);
+        } else {
+            sb.append(", ");
+        }
+    }
+
+    private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
         final StringBuilder sb = new StringBuilder();
+
+        if (indent != null) {
+            sb.append(indent);
+        }
+
         sb.append("ShortcutInfo {");
 
         sb.append("id=");
@@ -1787,47 +1809,51 @@
         sb.append(Integer.toHexString(mFlags));
         sb.append(" [");
         if (!isEnabled()) {
-            sb.append("X");
+            sb.append("Dis");
         }
         if (isImmutable()) {
             sb.append("Im");
         }
         if (isManifestShortcut()) {
-            sb.append("M");
+            sb.append("Man");
         }
         if (isDynamic()) {
-            sb.append("D");
+            sb.append("Dyn");
         }
         if (isPinned()) {
-            sb.append("P");
+            sb.append("Pin");
         }
         if (hasIconFile()) {
-            sb.append("If");
+            sb.append("Ic-f");
         }
         if (isIconPendingSave()) {
-            sb.append("^");
+            sb.append("Pens");
         }
         if (hasIconResource()) {
-            sb.append("Ir");
+            sb.append("Ic-r");
         }
         if (hasKeyFieldsOnly()) {
-            sb.append("K");
+            sb.append("Key");
         }
         if (hasStringResourcesResolved()) {
-            sb.append("Sr");
+            sb.append("Str");
         }
         if (isReturnedByServer()) {
-            sb.append("V");
+            sb.append("Rets");
         }
         sb.append("]");
 
-        sb.append(", packageName=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("packageName=");
         sb.append(mPackageName);
 
         sb.append(", activity=");
         sb.append(mActivity);
 
-        sb.append(", shortLabel=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("shortLabel=");
         sb.append(secure ? "***" : mTitle);
         sb.append(", resId=");
         sb.append(mTitleResId);
@@ -1835,7 +1861,9 @@
         sb.append(mTitleResName);
         sb.append("]");
 
-        sb.append(", longLabel=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("longLabel=");
         sb.append(secure ? "***" : mText);
         sb.append(", resId=");
         sb.append(mTextResId);
@@ -1843,7 +1871,9 @@
         sb.append(mTextResName);
         sb.append("]");
 
-        sb.append(", disabledMessage=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("disabledMessage=");
         sb.append(secure ? "***" : mDisabledMessage);
         sb.append(", resId=");
         sb.append(mDisabledMessageResId);
@@ -1851,19 +1881,27 @@
         sb.append(mDisabledMessageResName);
         sb.append("]");
 
-        sb.append(", categories=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("categories=");
         sb.append(mCategories);
 
-        sb.append(", icon=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("icon=");
         sb.append(mIcon);
 
-        sb.append(", rank=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("rank=");
         sb.append(mRank);
 
         sb.append(", timestamp=");
         sb.append(mLastChangedTimestamp);
 
-        sb.append(", intents=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("intents=");
         if (mIntents == null) {
             sb.append("null");
         } else {
@@ -1885,12 +1923,15 @@
             }
         }
 
-        sb.append(", extras=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("extras=");
         sb.append(mExtras);
 
         if (includeInternalData) {
+            addIndentOrComma(sb, indent);
 
-            sb.append(", iconRes=");
+            sb.append("iconRes=");
             sb.append(mIconResId);
             sb.append("[");
             sb.append(mIconResName);
diff --git a/android/content/res/Configuration.java b/android/content/res/Configuration.java
index 780e6f7..dfd3bbf 100644
--- a/android/content/res/Configuration.java
+++ b/android/content/res/Configuration.java
@@ -16,9 +16,20 @@
 
 package android.content.res;
 
+import static android.content.ConfigurationProto.DENSITY_DPI;
+import static android.content.ConfigurationProto.FONT_SCALE;
+import static android.content.ConfigurationProto.ORIENTATION;
+import static android.content.ConfigurationProto.SCREEN_HEIGHT_DP;
+import static android.content.ConfigurationProto.SCREEN_LAYOUT;
+import static android.content.ConfigurationProto.SCREEN_WIDTH_DP;
+import static android.content.ConfigurationProto.SMALLEST_SCREEN_WIDTH_DP;
+import static android.content.ConfigurationProto.UI_MODE;
+import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -27,6 +38,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.View;
 
 import com.android.internal.util.XmlUtils;
@@ -295,11 +307,12 @@
     public int screenLayout;
 
     /**
-     * @hide
      * Configuration relating to the windowing state of the object associated with this
      * Configuration. Contents of this field are not intended to affect resources, but need to be
      * communicated and propagated at the same time as the rest of Configuration.
+     * @hide
      */
+    @TestApi
     public final WindowConfiguration windowConfiguration = new WindowConfiguration();
 
     /** @hide */
@@ -1054,6 +1067,55 @@
     }
 
     /**
+     * Write to a protocol buffer output stream.
+     * Protocol buffer message definition at {@link android.content.ConfigurationProto}
+     *
+     * @param protoOutputStream Stream to write the Configuration object to.
+     * @param fieldId           Field Id of the Configuration as defined in the parent message
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        protoOutputStream.write(FONT_SCALE, fontScale);
+        protoOutputStream.write(SCREEN_LAYOUT, screenLayout);
+        protoOutputStream.write(ORIENTATION, orientation);
+        protoOutputStream.write(UI_MODE, uiMode);
+        protoOutputStream.write(SCREEN_WIDTH_DP, screenWidthDp);
+        protoOutputStream.write(SCREEN_HEIGHT_DP, screenHeightDp);
+        protoOutputStream.write(SMALLEST_SCREEN_WIDTH_DP, smallestScreenWidthDp);
+        protoOutputStream.write(DENSITY_DPI, densityDpi);
+        windowConfiguration.writeToProto(protoOutputStream, WINDOW_CONFIGURATION);
+        protoOutputStream.end(token);
+    }
+
+    /**
+     * Convert the UI mode to a human readable format.
+     * @hide
+     */
+    public static String uiModeToString(int uiMode) {
+        switch (uiMode) {
+            case UI_MODE_TYPE_UNDEFINED:
+                return "UI_MODE_TYPE_UNDEFINED";
+            case UI_MODE_TYPE_NORMAL:
+                return "UI_MODE_TYPE_NORMAL";
+            case UI_MODE_TYPE_DESK:
+                return "UI_MODE_TYPE_DESK";
+            case UI_MODE_TYPE_CAR:
+                return "UI_MODE_TYPE_CAR";
+            case UI_MODE_TYPE_TELEVISION:
+                return "UI_MODE_TYPE_TELEVISION";
+            case UI_MODE_TYPE_APPLIANCE:
+                return "UI_MODE_TYPE_APPLIANCE";
+            case UI_MODE_TYPE_WATCH:
+                return "UI_MODE_TYPE_WATCH";
+            case UI_MODE_TYPE_VR_HEADSET:
+                return "UI_MODE_TYPE_VR_HEADSET";
+            default:
+                return Integer.toString(uiMode);
+        }
+    }
+
+    /**
      * Set this object to the system defaults.
      */
     public void setToDefaults() {
diff --git a/android/content/res/Resources_Theme_Delegate.java b/android/content/res/Resources_Theme_Delegate.java
index f1e8fc2..8aa9216 100644
--- a/android/content/res/Resources_Theme_Delegate.java
+++ b/android/content/res/Resources_Theme_Delegate.java
@@ -56,7 +56,8 @@
             Resources thisResources, Theme thisTheme,
             int[] attrs) {
         boolean changed = setupResources(thisTheme);
-        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+                0, attrs);
         ta.setTheme(thisTheme);
         restoreResources(changed);
         return ta;
@@ -68,8 +69,8 @@
             int resid, int[] attrs)
             throws NotFoundException {
         boolean changed = setupResources(thisTheme);
-        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid,
-                attrs);
+        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+                resid, attrs);
         ta.setTheme(thisTheme);
         restoreResources(changed);
         return ta;
@@ -80,7 +81,7 @@
             Resources thisResources, Theme thisTheme,
             AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
         boolean changed = setupResources(thisTheme);
-        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set,
+        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(set,
                 attrs, defStyleAttr, defStyleRes);
         ta.setTheme(thisTheme);
         restoreResources(changed);
diff --git a/android/database/sqlite/SQLiteConnection.java b/android/database/sqlite/SQLiteConnection.java
index f894f05..c28583e 100644
--- a/android/database/sqlite/SQLiteConnection.java
+++ b/android/database/sqlite/SQLiteConnection.java
@@ -104,7 +104,7 @@
     private PreparedStatement mPreparedStatementPool;
 
     // The recent operations log.
-    private final OperationLog mRecentOperations = new OperationLog();
+    private final OperationLog mRecentOperations;
 
     // The native SQLiteConnection pointer.  (FOR INTERNAL USE ONLY)
     private long mConnectionPtr;
@@ -162,6 +162,7 @@
             SQLiteDatabaseConfiguration configuration,
             int connectionId, boolean primaryConnection) {
         mPool = pool;
+        mRecentOperations = new OperationLog(mPool);
         mConfiguration = new SQLiteDatabaseConfiguration(configuration);
         mConnectionId = connectionId;
         mIsPrimaryConnection = primaryConnection;
@@ -1298,6 +1299,11 @@
         private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
         private int mIndex;
         private int mGeneration;
+        private final SQLiteConnectionPool mPool;
+
+        OperationLog(SQLiteConnectionPool pool) {
+            mPool = pool;
+        }
 
         public int beginOperation(String kind, String sql, Object[] bindArgs) {
             synchronized (mOperations) {
@@ -1381,8 +1387,10 @@
                 }
                 operation.mEndTime = SystemClock.uptimeMillis();
                 operation.mFinished = true;
+                final long execTime = operation.mEndTime - operation.mStartTime;
+                mPool.onStatementExecuted(execTime);
                 return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
-                                operation.mEndTime - operation.mStartTime);
+                        execTime);
             }
             return false;
         }
@@ -1426,11 +1434,16 @@
                 int index = mIndex;
                 Operation operation = mOperations[index];
                 if (operation != null) {
+                    // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
+                    // and is relatively expensive to create during preloading. This method is only
+                    // used when dumping a connection, which is a rare (mainly error) case.
+                    SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                     int n = 0;
                     do {
                         StringBuilder msg = new StringBuilder();
                         msg.append("    ").append(n).append(": [");
-                        msg.append(operation.getFormattedStartTime());
+                        String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
+                        msg.append(formattedStartTime);
                         msg.append("] ");
                         operation.describe(msg, verbose);
                         printer.println(msg.toString());
@@ -1518,12 +1531,5 @@
             return methodName;
         }
 
-        private String getFormattedStartTime() {
-            // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created, and is
-            //       relatively expensive to create during preloading. This method is only used
-            //       when dumping a connection, which is a rare (mainly error) case. So:
-            //       DO NOT CACHE.
-            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(mStartWallTime));
-        }
     }
 }
diff --git a/android/database/sqlite/SQLiteConnectionPool.java b/android/database/sqlite/SQLiteConnectionPool.java
index b66bf18..8b0fef4 100644
--- a/android/database/sqlite/SQLiteConnectionPool.java
+++ b/android/database/sqlite/SQLiteConnectionPool.java
@@ -37,6 +37,7 @@
 import java.util.Map;
 import java.util.WeakHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.LockSupport;
 
 /**
@@ -102,6 +103,8 @@
     @GuardedBy("mLock")
     private IdleConnectionHandler mIdleConnectionHandler;
 
+    private final AtomicLong mTotalExecutionTimeCounter = new AtomicLong(0);
+
     // Describes what should happen to an acquired connection when it is returned to the pool.
     enum AcquiredConnectionStatus {
         // The connection should be returned to the pool as usual.
@@ -523,6 +526,10 @@
         mConnectionLeaked.set(true);
     }
 
+    void onStatementExecuted(long executionTimeMs) {
+        mTotalExecutionTimeCounter.addAndGet(executionTimeMs);
+    }
+
     // Can't throw.
     private void closeAvailableConnectionsAndLogExceptionsLocked() {
         closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
@@ -1076,6 +1083,7 @@
             printer.println("Connection pool for " + mConfiguration.path + ":");
             printer.println("  Open: " + mIsOpen);
             printer.println("  Max connections: " + mMaxConnectionPoolSize);
+            printer.println("  Total execution time: " + mTotalExecutionTimeCounter);
             if (mConfiguration.isLookasideConfigSet()) {
                 printer.println("  Lookaside config: sz=" + mConfiguration.lookasideSlotSize
                         + " cnt=" + mConfiguration.lookasideSlotCount);
diff --git a/android/ext/services/notification/Assistant.java b/android/ext/services/notification/Assistant.java
new file mode 100644
index 0000000..f535368
--- /dev/null
+++ b/android/ext/services/notification/Assistant.java
@@ -0,0 +1,165 @@
+/**
+ * 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.ext.services.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEGATIVE;
+
+import android.app.INotificationManager;
+import android.content.Context;
+import android.ext.services.R;
+import android.os.Bundle;
+import android.service.notification.Adjustment;
+import android.service.notification.NotificationAssistantService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Notification assistant that provides guidance on notification channel blocking
+ */
+public class Assistant extends NotificationAssistantService {
+    private static final String TAG = "ExtAssistant";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final ArrayList<Integer> DISMISS_WITH_PREJUDICE = new ArrayList<>();
+    static {
+        DISMISS_WITH_PREJUDICE.add(REASON_CANCEL);
+        DISMISS_WITH_PREJUDICE.add(REASON_LISTENER_CANCEL);
+    }
+
+    // key : impressions tracker
+    // TODO: persist across reboots
+    ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
+    // SBN key : channel id
+    ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
+
+    private Ranking mFakeRanking = null;
+
+    @Override
+    public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
+        if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
+        return null;
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
+        try {
+            Ranking ranking = getRanking(sbn.getKey(), rankingMap);
+            if (ranking != null && ranking.getChannel() != null) {
+                String key = getKey(
+                        sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId());
+                ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
+                        new ChannelImpressions());
+                if (ranking.getImportance() > IMPORTANCE_MIN && ci.shouldTriggerBlock()) {
+                    adjustNotification(createNegativeAdjustment(
+                            sbn.getPackageName(), sbn.getKey(), sbn.getUserId()));
+                }
+                mkeyToImpressions.put(key, ci);
+                mLiveNotifications.put(sbn.getKey(), ranking.getChannel().getId());
+            }
+        } catch (Throwable e) {
+            Log.e(TAG, "Error occurred processing post", e);
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            NotificationStats stats, int reason) {
+        try {
+            String channelId = mLiveNotifications.remove(sbn.getKey());
+            String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId);
+            ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, new ChannelImpressions());
+            if (stats.hasSeen()) {
+                ci.incrementViews();
+            }
+            if (DISMISS_WITH_PREJUDICE.contains(reason)
+                    && !sbn.isAppGroup()
+                    && !sbn.getNotification().isGroupChild()
+                    && !stats.hasInteracted()
+                    && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
+                    && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
+                    && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
+               if (DEBUG) Log.i(TAG, "increment dismissals");
+                ci.incrementDismissals();
+            } else {
+                if (DEBUG) Slog.i(TAG, "reset streak");
+                ci.resetStreak();
+            }
+            mkeyToImpressions.put(key, ci);
+        } catch (Throwable e) {
+            Slog.e(TAG, "Error occurred processing removal", e);
+        }
+    }
+
+    @Override
+    public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
+            String snoozeCriterionId) {
+    }
+
+    @Override
+    public void onListenerConnected() {
+        if (DEBUG) Log.i(TAG, "CONNECTED");
+        try {
+            for (StatusBarNotification sbn : getActiveNotifications()) {
+                onNotificationPosted(sbn);
+            }
+        } catch (Throwable e) {
+            Log.e(TAG, "Error occurred on connection", e);
+        }
+    }
+
+    private String getKey(String pkg, int userId, String channelId) {
+        return pkg + "|" + userId + "|" + channelId;
+    }
+
+    private Ranking getRanking(String key, RankingMap rankingMap) {
+        if (mFakeRanking != null) {
+            return mFakeRanking;
+        }
+        Ranking ranking = new Ranking();
+        rankingMap.getRanking(key, ranking);
+        return ranking;
+    }
+
+    private Adjustment createNegativeAdjustment(String packageName, String key, int user) {
+        if (DEBUG) Log.d(TAG, "User probably doesn't want " + key);
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
+        return new Adjustment(packageName, key,  signals,
+                getContext().getString(R.string.prompt_block_reason), user);
+    }
+
+    // for testing
+    protected void setFakeRanking(Ranking ranking) {
+        mFakeRanking = ranking;
+    }
+
+    protected void setNoMan(INotificationManager noMan) {
+        mNoMan = noMan;
+    }
+
+    protected void setContext(Context context) {
+        mSystemContext = context;
+    }
+}
\ No newline at end of file
diff --git a/android/ext/services/notification/ChannelImpressions.java b/android/ext/services/notification/ChannelImpressions.java
new file mode 100644
index 0000000..30567cc
--- /dev/null
+++ b/android/ext/services/notification/ChannelImpressions.java
@@ -0,0 +1,137 @@
+/**
+ * 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.ext.services.notification;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+public final class ChannelImpressions implements Parcelable {
+    private static final String TAG = "ExtAssistant.CI";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    static final double DISMISS_TO_VIEW_RATIO_LIMIT = .8;
+    static final int STREAK_LIMIT = 2;
+
+    private int mDismissals = 0;
+    private int mViews = 0;
+    private int mStreak = 0;
+
+    public ChannelImpressions() {
+    }
+
+    public ChannelImpressions(int dismissals, int views) {
+        mDismissals = dismissals;
+        mViews = views;
+    }
+
+    protected ChannelImpressions(Parcel in) {
+        mDismissals = in.readInt();
+        mViews = in.readInt();
+        mStreak = in.readInt();
+    }
+
+    public int getStreak() {
+        return mStreak;
+    }
+
+    public int getDismissals() {
+        return mDismissals;
+    }
+
+    public int getViews() {
+        return mViews;
+    }
+
+    public void incrementDismissals() {
+        mDismissals++;
+        mStreak++;
+    }
+
+    public void incrementViews() {
+        mViews++;
+    }
+
+    public void resetStreak() {
+        mStreak = 0;
+    }
+
+    public boolean shouldTriggerBlock() {
+        if (getViews() == 0) {
+            return false;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "should trigger? " + getDismissals() + " " + getViews() + " " + getStreak());
+        }
+        return ((double) getDismissals() / getViews()) > DISMISS_TO_VIEW_RATIO_LIMIT
+                && getStreak() > STREAK_LIMIT;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mDismissals);
+        dest.writeInt(mViews);
+        dest.writeInt(mStreak);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ChannelImpressions> CREATOR = new Creator<ChannelImpressions>() {
+        @Override
+        public ChannelImpressions createFromParcel(Parcel in) {
+            return new ChannelImpressions(in);
+        }
+
+        @Override
+        public ChannelImpressions[] newArray(int size) {
+            return new ChannelImpressions[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ChannelImpressions that = (ChannelImpressions) o;
+
+        if (mDismissals != that.mDismissals) return false;
+        if (mViews != that.mViews) return false;
+        return mStreak == that.mStreak;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mDismissals;
+        result = 31 * result + mViews;
+        result = 31 * result + mStreak;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("ChannelImpressions{");
+        sb.append("mDismissals=").append(mDismissals);
+        sb.append(", mViews=").append(mViews);
+        sb.append(", mStreak=").append(mStreak);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/android/graphics/BidiRenderer.java b/android/graphics/BidiRenderer.java
index 9664a58..7b7dfa6 100644
--- a/android/graphics/BidiRenderer.java
+++ b/android/graphics/BidiRenderer.java
@@ -200,8 +200,7 @@
 
     private static void logFontWarning() {
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
-                "Some fonts could not be loaded. The rendering may not be perfect. " +
-                        "Try running the IDE with JRE 7.", null, null);
+                "Some fonts could not be loaded. The rendering may not be perfect.", null, null);
     }
 
     /**
@@ -288,15 +287,17 @@
     @NonNull
     private static Font getScriptFont(char[] text, int start, int limit, List<FontInfo> fonts) {
         for (FontInfo fontInfo : fonts) {
-            if (fontInfo.mFont == null) {
-                logFontWarning();
-                continue;
-            }
             if (fontInfo.mFont.canDisplayUpTo(text, start, limit) == -1) {
                 return fontInfo.mFont;
             }
         }
 
+        if (fonts.isEmpty()) {
+            logFontWarning();
+            // Fallback font in case no font can be loaded
+            return Font.getFont(Font.SERIF);
+        }
+
         return fonts.get(0).mFont;
     }
 
diff --git a/android/graphics/ImageFormat.java b/android/graphics/ImageFormat.java
index e3527e3..43fd270 100644
--- a/android/graphics/ImageFormat.java
+++ b/android/graphics/ImageFormat.java
@@ -658,6 +658,14 @@
      *    float confidence = floatDepthBuffer.get();
      * </pre>
      *
+     * For camera devices that support the
+     * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}
+     * capability, DEPTH_POINT_CLOUD coordinates have units of meters, and the coordinate system is
+     * defined by the camera's pose transforms:
+     * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_TRANSLATION} and
+     * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_ROTATION}. That means the origin is
+     * the optical center of the camera device, and the positive Z axis points along the camera's optical axis,
+     * toward the scene.
      */
     public static final int DEPTH_POINT_CLOUD = 0x101;
 
diff --git a/android/graphics/NinePatch_Delegate.java b/android/graphics/NinePatch_Delegate.java
index 43e5b0f..ce2c18b 100644
--- a/android/graphics/NinePatch_Delegate.java
+++ b/android/graphics/NinePatch_Delegate.java
@@ -160,8 +160,12 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nativeFinalize(long chunk) {
-        sManager.removeJavaReferenceFor(chunk);
+    /*package*/ static void nativeFinalize(long nativeNinePatch) {
+        NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
+        if (delegate != null && delegate.chunk != null) {
+            sChunkCache.remove(delegate.chunk);
+        }
+        sManager.removeJavaReferenceFor(nativeNinePatch);
     }
 
 
diff --git a/android/graphics/PixelFormat.java b/android/graphics/PixelFormat.java
index f93886d..96d6eee 100644
--- a/android/graphics/PixelFormat.java
+++ b/android/graphics/PixelFormat.java
@@ -185,4 +185,52 @@
 
         return false;
     }
+
+    /**
+     * @hide
+     */
+    public static String formatToString(@Format int format) {
+        switch (format) {
+            case UNKNOWN:
+                return "UNKNOWN";
+            case TRANSLUCENT:
+                return "TRANSLUCENT";
+            case TRANSPARENT:
+                return "TRANSPARENT";
+            case RGBA_8888:
+                return "RGBA_8888";
+            case RGBX_8888:
+                return "RGBX_8888";
+            case RGB_888:
+                return "RGB_888";
+            case RGB_565:
+                return "RGB_565";
+            case RGBA_5551:
+                return "RGBA_5551";
+            case RGBA_4444:
+                return "RGBA_4444";
+            case A_8:
+                return "A_8";
+            case L_8:
+                return "L_8";
+            case LA_88:
+                return "LA_88";
+            case RGB_332:
+                return "RGB_332";
+            case YCbCr_422_SP:
+                return "YCbCr_422_SP";
+            case YCbCr_420_SP:
+                return "YCbCr_420_SP";
+            case YCbCr_422_I:
+                return "YCbCr_422_I";
+            case RGBA_F16:
+                return "RGBA_F16";
+            case RGBA_1010102:
+                return "RGBA_1010102";
+            case JPEG:
+                return "JPEG";
+            default:
+                return Integer.toString(format);
+        }
+    }
 }
diff --git a/android/graphics/RadialGradient_Delegate.java b/android/graphics/RadialGradient_Delegate.java
index 1defc90..25521d2 100644
--- a/android/graphics/RadialGradient_Delegate.java
+++ b/android/graphics/RadialGradient_Delegate.java
@@ -173,6 +173,7 @@
                 int index = 0;
                 float[] pt1 = new float[2];
                 float[] pt2 = new float[2];
+
                 for (int iy = 0 ; iy < h ; iy++) {
                     for (int ix = 0 ; ix < w ; ix++) {
                         // handle the canvas transform
@@ -181,12 +182,12 @@
                         mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
 
                         // handle the local matrix
-                        pt1[0] = pt2[0] - mX;
-                        pt1[1] = pt2[1] - mY;
+                        pt1[0] = pt2[0];
+                        pt1[1] = pt2[1];
                         mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
 
-                        float _x = pt2[0];
-                        float _y = pt2[1];
+                        float _x = pt2[0] - mX;
+                        float _y = pt2[1] - mY;
                         float distance = (float) Math.hypot(_x, _y);
 
                         data[index++] = getGradientColor(distance / mRadius);
diff --git a/android/graphics/Shader.java b/android/graphics/Shader.java
index 0209cea..40288f5 100644
--- a/android/graphics/Shader.java
+++ b/android/graphics/Shader.java
@@ -159,8 +159,10 @@
         if (mNativeInstance == 0) {
             mNativeInstance = createNativeInstance(mLocalMatrix == null
                     ? 0 : mLocalMatrix.native_instance);
-            mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
-                    this, mNativeInstance);
+            if (mNativeInstance != 0) {
+                mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+                        this, mNativeInstance);
+            }
         }
         return mNativeInstance;
     }
diff --git a/android/graphics/drawable/RippleBackground.java b/android/graphics/drawable/RippleBackground.java
index 6bd2646..3bf4f90 100644
--- a/android/graphics/drawable/RippleBackground.java
+++ b/android/graphics/drawable/RippleBackground.java
@@ -104,7 +104,7 @@
         final AnimatorSet set = new AnimatorSet();
 
         // Linear exit after enter is completed.
-        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 0);
+        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, OPACITY, 0);
         exit.setInterpolator(LINEAR_INTERPOLATOR);
         exit.setDuration(OPACITY_EXIT_DURATION);
         exit.setAutoCancel(true);
@@ -115,7 +115,7 @@
         final int fastEnterDuration = mIsBounded ?
                 (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
         if (fastEnterDuration > 0) {
-            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 1);
+            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, OPACITY, 1);
             enter.setInterpolator(LINEAR_INTERPOLATOR);
             enter.setDuration(fastEnterDuration);
             enter.setAutoCancel(true);
diff --git a/android/graphics/drawable/RippleDrawable.java b/android/graphics/drawable/RippleDrawable.java
index 8f314c9..1727eca 100644
--- a/android/graphics/drawable/RippleDrawable.java
+++ b/android/graphics/drawable/RippleDrawable.java
@@ -266,9 +266,9 @@
             }
         }
 
-        setRippleActive(enabled && pressed);
-        setBackgroundActive(hovered || focused || (enabled && pressed), focused || hovered);
+        setRippleActive(focused || (enabled && pressed));
 
+        setBackgroundActive(hovered, hovered);
         return changed;
     }
 
@@ -693,7 +693,9 @@
         // have a mask or content and the ripple bounds if we're projecting.
         final Rect bounds = getDirtyBounds();
         final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
-        canvas.clipRect(bounds);
+        if (isBounded()) {
+            canvas.clipRect(bounds);
+        }
 
         drawContent(canvas);
         drawBackgroundAndRipples(canvas);
diff --git a/android/graphics/drawable/RippleForeground.java b/android/graphics/drawable/RippleForeground.java
index 829733e..a675eaf 100644
--- a/android/graphics/drawable/RippleForeground.java
+++ b/android/graphics/drawable/RippleForeground.java
@@ -41,7 +41,6 @@
 
     // Pixel-based accelerations and velocities.
     private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
-    private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
     private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
 
     // Bounded ripple animation properties.
@@ -80,17 +79,18 @@
     private float mTweenX = 0;
     private float mTweenY = 0;
 
-    /** Whether this ripple is bounded. */
-    private boolean mIsBounded;
-
     /** Whether this ripple has finished its exit animation. */
     private boolean mHasFinishedExit;
 
+    /**
+     * If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
+     */
+    private float mStartRadius = 0;
+
     public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
             boolean isBounded, boolean forceSoftware) {
         super(owner, bounds, forceSoftware);
 
-        mIsBounded = isBounded;
         mStartingX = startingX;
         mStartingY = startingY;
 
@@ -100,6 +100,8 @@
         } else {
             mBoundedRadius = 0;
         }
+        // Take 60% of the maximum of the width and height, then divided half to get the radius.
+        mStartRadius = Math.max(bounds.width(), bounds.height()) * 0.3f;
     }
 
     @Override
@@ -162,24 +164,18 @@
 
     @Override
     protected Animator createSoftwareEnter(boolean fast) {
-        // Bounded ripples don't have enter animations.
-        if (mIsBounded) {
-            return null;
-        }
-
-        final int duration = (int)
-                (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensityScale) + 0.5);
+        final int duration = getRadiusDuration();
 
         final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
         tweenRadius.setAutoCancel(true);
         tweenRadius.setDuration(duration);
-        tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
+        tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
         tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
 
         final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
         tweenOrigin.setAutoCancel(true);
         tweenOrigin.setDuration(duration);
-        tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
+        tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
         tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
 
         final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
@@ -201,45 +197,29 @@
         return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
     }
 
-    private int getRadiusExitDuration() {
+    private int getRadiusDuration() {
         final float remainingRadius = mTargetRadius - getCurrentRadius();
-        return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
-                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensityScale) + 0.5);
+        return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION *
+                mDensityScale) + 0.5);
     }
 
     private float getCurrentRadius() {
-        return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+        return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
     }
 
     private int getOpacityExitDuration() {
         return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
     }
 
-    /**
-     * Compute target values that are dependent on bounding.
-     */
-    private void computeBoundedTargetValues() {
-        mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
-        mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
-        mTargetRadius = mBoundedRadius;
-    }
-
     @Override
     protected Animator createSoftwareExit() {
         final int radiusDuration;
         final int originDuration;
         final int opacityDuration;
-        if (mIsBounded) {
-            computeBoundedTargetValues();
 
-            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
-            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
-            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
-        } else {
-            radiusDuration = getRadiusExitDuration();
-            originDuration = radiusDuration;
-            opacityDuration = getOpacityExitDuration();
-        }
+        radiusDuration = getRadiusDuration();
+        originDuration = radiusDuration;
+        opacityDuration = getOpacityExitDuration();
 
         final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
         tweenRadius.setAutoCancel(true);
@@ -268,17 +248,10 @@
         final int radiusDuration;
         final int originDuration;
         final int opacityDuration;
-        if (mIsBounded) {
-            computeBoundedTargetValues();
 
-            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
-            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
-            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
-        } else {
-            radiusDuration = getRadiusExitDuration();
-            originDuration = radiusDuration;
-            opacityDuration = getOpacityExitDuration();
-        }
+        radiusDuration = getRadiusDuration();
+        originDuration = radiusDuration;
+        opacityDuration = getOpacityExitDuration();
 
         final float startX = getCurrentX();
         final float startY = getCurrentY();
diff --git a/android/graphics/drawable/VectorDrawable_Delegate.java b/android/graphics/drawable/VectorDrawable_Delegate.java
index 5fa7102..0063046 100644
--- a/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -35,6 +35,7 @@
 import android.graphics.PathMeasure;
 import android.graphics.Path_Delegate;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.graphics.Region.Op;
 import android.graphics.Shader_Delegate;
 import android.util.ArrayMap;
@@ -144,6 +145,9 @@
         VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
 
         Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+        Canvas_Delegate.nClipRect(canvasWrapperPtr,
+                bounds.left, bounds.top, bounds.right, bounds.bottom,
+                Region.Op.INTERSECT.nativeInt);
         Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
 
         if (needsMirroring) {
diff --git a/android/hardware/Camera.java b/android/hardware/Camera.java
index aa35a66..931b5c9 100644
--- a/android/hardware/Camera.java
+++ b/android/hardware/Camera.java
@@ -16,10 +16,11 @@
 
 package android.hardware;
 
-import android.app.ActivityThread;
+import static android.system.OsConstants.*;
+
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.app.job.JobInfo;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.Point;
@@ -34,11 +35,11 @@
 import android.os.ServiceManager;
 import android.renderscript.Allocation;
 import android.renderscript.Element;
-import android.renderscript.RenderScript;
 import android.renderscript.RSIllegalArgumentException;
+import android.renderscript.RenderScript;
 import android.renderscript.Type;
-import android.util.Log;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 
@@ -48,8 +49,6 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 
-import static android.system.OsConstants.*;
-
 /**
  * The Camera class is used to set image capture settings, start/stop preview,
  * snap pictures, and retrieve frames for encoding for video.  This class is a
@@ -243,12 +242,19 @@
 
     /**
      * Returns the number of physical cameras available on this device.
+     *
+     * @return total number of accessible camera devices, or 0 if there are no
+     *   cameras or an error was encountered enumerating them.
      */
     public native static int getNumberOfCameras();
 
     /**
      * Returns the information about a particular camera.
      * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
+     *
+     * @throws RuntimeException if an invalid ID is provided, or if there is an
+     *    error retrieving the information (generally due to a hardware or other
+     *    low-level failure).
      */
     public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
         _getCameraInfo(cameraId, cameraInfo);
@@ -362,7 +368,10 @@
     /**
      * Creates a new Camera object to access the first back-facing camera on the
      * device. If the device does not have a back-facing camera, this returns
-     * null.
+     * null. Otherwise acts like the {@link #open(int)} call.
+     *
+     * @return a new Camera object for the first back-facing camera, or null if there is no
+     *  backfacing camera
      * @see #open(int)
      */
     public static Camera open() {
@@ -609,6 +618,8 @@
      *
      * @throws IOException if a connection cannot be re-established (for
      *     example, if the camera is still in use by another process).
+     * @throws RuntimeException if release() has been called on this Camera
+     *     instance.
      */
     public native final void reconnect() throws IOException;
 
@@ -637,6 +648,8 @@
      *     or null to remove the preview surface
      * @throws IOException if the method fails (for example, if the surface
      *     is unavailable or unsuitable).
+     * @throws RuntimeException if release() has been called on this Camera
+     *    instance.
      */
     public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
         if (holder != null) {
@@ -684,6 +697,8 @@
      *     texture
      * @throws IOException if the method fails (for example, if the surface
      *     texture is unavailable or unsuitable).
+     * @throws RuntimeException if release() has been called on this Camera
+     *    instance.
      */
     public native final void setPreviewTexture(SurfaceTexture surfaceTexture) throws IOException;
 
@@ -733,12 +748,20 @@
      * {@link #setPreviewCallbackWithBuffer(Camera.PreviewCallback)} were
      * called, {@link Camera.PreviewCallback#onPreviewFrame(byte[], Camera)}
      * will be called when preview data becomes available.
+     *
+     * @throws RuntimeException if starting preview fails; usually this would be
+     *    because of a hardware or other low-level error, or because release()
+     *    has been called on this Camera instance.
      */
     public native final void startPreview();
 
     /**
      * Stops capturing and drawing preview frames to the surface, and
      * resets the camera for a future call to {@link #startPreview()}.
+     *
+     * @throws RuntimeException if stopping preview fails; usually this would be
+     *    because of a hardware or other low-level error, or because release()
+     *    has been called on this Camera instance.
      */
     public final void stopPreview() {
         _stopPreview();
@@ -777,6 +800,8 @@
      *
      * @param cb a callback object that receives a copy of each preview frame,
      *     or null to stop receiving callbacks.
+     * @throws RuntimeException if release() has been called on this Camera
+     *     instance.
      * @see android.media.MediaActionSound
      */
     public final void setPreviewCallback(PreviewCallback cb) {
@@ -803,6 +828,8 @@
      *
      * @param cb a callback object that receives a copy of the next preview frame,
      *     or null to stop receiving callbacks.
+     * @throws RuntimeException if release() has been called on this Camera
+     *     instance.
      * @see android.media.MediaActionSound
      */
     public final void setOneShotPreviewCallback(PreviewCallback cb) {
@@ -840,6 +867,8 @@
      *
      * @param cb a callback object that receives a copy of the preview frame,
      *     or null to stop receiving callbacks and clear the buffer queue.
+     * @throws RuntimeException if release() has been called on this Camera
+     *     instance.
      * @see #addCallbackBuffer(byte[])
      * @see android.media.MediaActionSound
      */
@@ -1259,6 +1288,9 @@
      * success sound to the user.</p>
      *
      * @param cb the callback to run
+     * @throws RuntimeException if starting autofocus fails; usually this would
+     *    be because of a hardware or other low-level error, or because
+     *    release() has been called on this Camera instance.
      * @see #cancelAutoFocus()
      * @see android.hardware.Camera.Parameters#setAutoExposureLock(boolean)
      * @see android.hardware.Camera.Parameters#setAutoWhiteBalanceLock(boolean)
@@ -1279,6 +1311,9 @@
      * this function will return the focus position to the default.
      * If the camera does not support auto-focus, this is a no-op.
      *
+     * @throws RuntimeException if canceling autofocus fails; usually this would
+     *    be because of a hardware or other low-level error, or because
+     *    release() has been called on this Camera instance.
      * @see #autoFocus(Camera.AutoFocusCallback)
      */
     public final void cancelAutoFocus()
@@ -1333,6 +1368,9 @@
      * Sets camera auto-focus move callback.
      *
      * @param cb the callback to run
+     * @throws RuntimeException if enabling the focus move callback fails;
+     *    usually this would be because of a hardware or other low-level error,
+     *    or because release() has been called on this Camera instance.
      */
     public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {
         mAutoFocusMoveCallback = cb;
@@ -1384,7 +1422,7 @@
     };
 
     /**
-     * Equivalent to takePicture(shutter, raw, null, jpeg).
+     * Equivalent to <pre>takePicture(Shutter, raw, null, jpeg)</pre>.
      *
      * @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
      */
@@ -1422,6 +1460,9 @@
      * @param raw       the callback for raw (uncompressed) image data, or null
      * @param postview  callback with postview image data, may be null
      * @param jpeg      the callback for JPEG image data, or null
+     * @throws RuntimeException if starting picture capture fails; usually this
+     *    would be because of a hardware or other low-level error, or because
+     *    release() has been called on this Camera instance.
      */
     public final void takePicture(ShutterCallback shutter, PictureCallback raw,
             PictureCallback postview, PictureCallback jpeg) {
@@ -1534,6 +1575,9 @@
      *
      * @param degrees the angle that the picture will be rotated clockwise.
      *                Valid values are 0, 90, 180, and 270.
+     * @throws RuntimeException if setting orientation fails; usually this would
+     *    be because of a hardware or other low-level error, or because
+     *    release() has been called on this Camera instance.
      * @see #setPreviewDisplay(SurfaceHolder)
      */
     public native final void setDisplayOrientation(int degrees);
@@ -1559,6 +1603,9 @@
      *         changed. {@code false} if the shutter sound state could not be
      *         changed. {@code true} is also returned if shutter sound playback
      *         is already set to the requested state.
+     * @throws RuntimeException if the call fails; usually this would be because
+     *    of a hardware or other low-level error, or because release() has been
+     *    called on this Camera instance.
      * @see #takePicture
      * @see CameraInfo#canDisableShutterSound
      * @see ShutterCallback
@@ -1903,6 +1950,9 @@
      * If modifications are made to the returned Parameters, they must be passed
      * to {@link #setParameters(Camera.Parameters)} to take effect.
      *
+     * @throws RuntimeException if reading parameters fails; usually this would
+     *    be because of a hardware or other low-level error, or because
+     *    release() has been called on this Camera instance.
      * @see #setParameters(Camera.Parameters)
      */
     public Parameters getParameters() {
diff --git a/android/hardware/LegacySensorManager.java b/android/hardware/LegacySensorManager.java
index f5cf3f7..098121d 100644
--- a/android/hardware/LegacySensorManager.java
+++ b/android/hardware/LegacySensorManager.java
@@ -204,7 +204,7 @@
     }
 
     private static final class LegacyListener implements SensorEventListener {
-        private float mValues[] = new float[6];
+        private float[] mValues = new float[6];
         private SensorListener mTarget;
         private int mSensors;
         private final LmsFilter mYawfilter = new LmsFilter();
@@ -256,7 +256,7 @@
         }
 
         public void onSensorChanged(SensorEvent event) {
-            final float v[] = mValues;
+            final float[] v = mValues;
             v[0] = event.values[0];
             v[1] = event.values[1];
             v[2] = event.values[2];
@@ -264,10 +264,10 @@
             int legacyType = getLegacySensorType(type);
             mapSensorDataToWindow(legacyType, v, LegacySensorManager.getRotation());
             if (type == Sensor.TYPE_ORIENTATION) {
-                if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW)!=0) {
+                if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW) != 0) {
                     mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION_RAW, v);
                 }
-                if ((mSensors & SensorManager.SENSOR_ORIENTATION)!=0) {
+                if ((mSensors & SensorManager.SENSOR_ORIENTATION) != 0) {
                     v[0] = mYawfilter.filter(event.timestamp, v[0]);
                     mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION, v);
                 }
@@ -317,7 +317,7 @@
                 switch (sensor) {
                     case SensorManager.SENSOR_ACCELEROMETER:
                     case SensorManager.SENSOR_MAGNETIC_FIELD:
-                        values[0] =-y;
+                        values[0] = -y;
                         values[1] = x;
                         values[2] = z;
                         break;
@@ -337,15 +337,15 @@
                 switch (sensor) {
                     case SensorManager.SENSOR_ACCELEROMETER:
                     case SensorManager.SENSOR_MAGNETIC_FIELD:
-                        values[0] =-x;
-                        values[1] =-y;
+                        values[0] = -x;
+                        values[1] = -y;
                         values[2] = z;
                         break;
                     case SensorManager.SENSOR_ORIENTATION:
                     case SensorManager.SENSOR_ORIENTATION_RAW:
                         values[0] = (x >= 180) ? (x - 180) : (x + 180);
-                        values[1] =-y;
-                        values[2] =-z;
+                        values[1] = -y;
+                        values[2] = -z;
                         break;
                 }
             }
@@ -369,10 +369,11 @@
     private static final class LmsFilter {
         private static final int SENSORS_RATE_MS = 20;
         private static final int COUNT = 12;
-        private static final float PREDICTION_RATIO = 1.0f/3.0f;
-        private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
-        private float mV[] = new float[COUNT*2];
-        private long mT[] = new long[COUNT*2];
+        private static final float PREDICTION_RATIO = 1.0f / 3.0f;
+        private static final float PREDICTION_TIME =
+                (SENSORS_RATE_MS * COUNT / 1000.0f) * PREDICTION_RATIO;
+        private float[] mV = new float[COUNT * 2];
+        private long[] mT = new long[COUNT * 2];
         private int mIndex;
 
         public LmsFilter() {
@@ -383,9 +384,9 @@
             float v = in;
             final float ns = 1.0f / 1000000000.0f;
             float v1 = mV[mIndex];
-            if ((v-v1) > 180) {
+            if ((v - v1) > 180) {
                 v -= 360;
-            } else if ((v1-v) > 180) {
+            } else if ((v1 - v) > 180) {
                 v += 360;
             }
             /* Manage the circular buffer, we write the data twice spaced
@@ -393,40 +394,43 @@
              * when it's full
              */
             mIndex++;
-            if (mIndex >= COUNT*2)
+            if (mIndex >= COUNT * 2) {
                 mIndex = COUNT;
+            }
             mV[mIndex] = v;
             mT[mIndex] = time;
-            mV[mIndex-COUNT] = v;
-            mT[mIndex-COUNT] = time;
+            mV[mIndex - COUNT] = v;
+            mT[mIndex - COUNT] = time;
 
             float A, B, C, D, E;
             float a, b;
             int i;
 
             A = B = C = D = E = 0;
-            for (i=0 ; i<COUNT-1 ; i++) {
+            for (i = 0; i < COUNT - 1; i++) {
                 final int j = mIndex - 1 - i;
                 final float Z = mV[j];
-                final float T = (mT[j]/2 + mT[j+1]/2 - time)*ns;
-                float dT = (mT[j] - mT[j+1])*ns;
+                final float T = (mT[j] / 2 + mT[j + 1] / 2 - time) * ns;
+                float dT = (mT[j] - mT[j + 1]) * ns;
                 dT *= dT;
-                A += Z*dT;
-                B += T*(T*dT);
-                C +=   (T*dT);
-                D += Z*(T*dT);
+                A += Z * dT;
+                B += T * (T * dT);
+                C += (T * dT);
+                D += Z * (T * dT);
                 E += dT;
             }
-            b = (A*B + C*D) / (E*B + C*C);
-            a = (E*b - A) / C;
-            float f = b + PREDICTION_TIME*a;
+            b = (A * B + C * D) / (E * B + C * C);
+            a = (E * b - A) / C;
+            float f = b + PREDICTION_TIME * a;
 
             // Normalize
             f *= (1.0f / 360.0f);
-            if (((f>=0)?f:-f) >= 0.5f)
-                f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
-            if (f < 0)
+            if (((f >= 0) ? f : -f) >= 0.5f) {
+                f = f - (float) Math.ceil(f + 0.5f) + 1.0f;
+            }
+            if (f < 0) {
                 f += 1.0f;
+            }
             f *= 360.0f;
             return f;
         }
diff --git a/android/hardware/Sensor.java b/android/hardware/Sensor.java
index f02e484..7fb0c89 100644
--- a/android/hardware/Sensor.java
+++ b/android/hardware/Sensor.java
@@ -794,12 +794,12 @@
             1, // SENSOR_TYPE_PICK_UP_GESTURE
             1, // SENSOR_TYPE_WRIST_TILT_GESTURE
             1, // SENSOR_TYPE_DEVICE_ORIENTATION
-            16,// SENSOR_TYPE_POSE_6DOF
+            16, // SENSOR_TYPE_POSE_6DOF
             1, // SENSOR_TYPE_STATIONARY_DETECT
             1, // SENSOR_TYPE_MOTION_DETECT
             1, // SENSOR_TYPE_HEART_BEAT
             2, // SENSOR_TYPE_DYNAMIC_SENSOR_META
-            16,// skip over additional sensor info type
+            16, // skip over additional sensor info type
             1, // SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT
             6, // SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED
     };
@@ -857,8 +857,8 @@
     static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
         // RotationVector length has changed to 3 to 5 for API level 18
         // Set it to 3 for backward compatibility.
-        if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR &&
-                sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR
+                && sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
             return 3;
         }
         int offset = sensor.mType;
@@ -1033,9 +1033,9 @@
      * Returns true if the sensor is a wake-up sensor.
      * <p>
      * <b>Application Processor Power modes</b> <p>
-     * Application Processor(AP), is the processor on which applications run.  When no wake lock is held
-     * and the user is not interacting with the device, this processor can enter a “Suspend” mode,
-     * reducing the power consumption by 10 times or more.
+     * Application Processor(AP), is the processor on which applications run.  When no wake lock is
+     * held and the user is not interacting with the device, this processor can enter a “Suspend”
+     * mode, reducing the power consumption by 10 times or more.
      * </p>
      * <p>
      * <b>Non-wake-up sensors</b> <p>
@@ -1232,6 +1232,6 @@
      */
     private void setUuid(long msb, long lsb) {
         // TODO(b/29547335): Rename this method to setId.
-        mId = (int)msb;
+        mId = (int) msb;
     }
 }
diff --git a/android/hardware/SensorAdditionalInfo.java b/android/hardware/SensorAdditionalInfo.java
index 0c6a415..7c876cf 100644
--- a/android/hardware/SensorAdditionalInfo.java
+++ b/android/hardware/SensorAdditionalInfo.java
@@ -200,7 +200,7 @@
     public static final int TYPE_DEBUG_INFO  = 0x40000000;
 
     SensorAdditionalInfo(
-            Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) {
+            Sensor aSensor, int aType, int aSerial, int[] aIntValues, float[] aFloatValues) {
         sensor = aSensor;
         type = aType;
         serial = aSerial;
@@ -222,10 +222,10 @@
                 null, new float[] { strength, declination, inclination});
     }
     /** @hide */
-    public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float [] data) {
+    public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float[] data) {
         if (type < TYPE_CUSTOM_INFO || type >= TYPE_DEBUG_INFO || aSensor == null) {
-            throw new IllegalArgumentException("invalid parameter(s): type: " + type +
-                    "; sensor: " + aSensor);
+            throw new IllegalArgumentException(
+                    "invalid parameter(s): type: " + type + "; sensor: " + aSensor);
         }
 
         return new SensorAdditionalInfo(aSensor, type, 0, null, data);
diff --git a/android/hardware/SensorEvent.java b/android/hardware/SensorEvent.java
index c0bca97..bbd04a3 100644
--- a/android/hardware/SensorEvent.java
+++ b/android/hardware/SensorEvent.java
@@ -207,8 +207,8 @@
      *          timestamp = event.timestamp;
      *          float[] deltaRotationMatrix = new float[9];
      *          SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
-     *          // User code should concatenate the delta rotation we computed with the current rotation
-     *          // in order to get the updated rotation.
+     *          // User code should concatenate the delta rotation we computed with the current
+     *          // rotation in order to get the updated rotation.
      *          // rotationCurrent = rotationCurrent * deltaRotationMatrix;
      *     }
      * </pre>
@@ -244,21 +244,22 @@
      *  <h4>{@link android.hardware.Sensor#TYPE_GRAVITY Sensor.TYPE_GRAVITY}:</h4>
      *  <p>A three dimensional vector indicating the direction and magnitude of gravity.  Units
      *  are m/s^2. The coordinate system is the same as is used by the acceleration sensor.</p>
-     *  <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be identical
-     *  to that of the accelerometer.</p>
+     *  <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be
+     *  identical to that of the accelerometer.</p>
      *
-     *  <h4>{@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:</h4>
-     *  A three dimensional vector indicating acceleration along each device axis, not including
-     *  gravity.  All values have units of m/s^2.  The coordinate system is the same as is used by the
-     *  acceleration sensor.
+     *  <h4>
+     *  {@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:
+     *  </h4> A three dimensional vector indicating acceleration along each device axis, not
+     *  including gravity. All values have units of m/s^2.  The coordinate system is the same as is
+     *  used by the acceleration sensor.
      *  <p>The output of the accelerometer, gravity and  linear-acceleration sensors must obey the
      *  following relation:</p>
-     *   <p><ul>acceleration = gravity + linear-acceleration</ul></p>
+     *  <p><ul>acceleration = gravity + linear-acceleration</ul></p>
      *
      *  <h4>{@link android.hardware.Sensor#TYPE_ROTATION_VECTOR Sensor.TYPE_ROTATION_VECTOR}:</h4>
-     *  <p>The rotation vector represents the orientation of the device as a combination of an <i>angle</i>
-     *  and an <i>axis</i>, in which the device has rotated through an angle &#952 around an axis
-     *  &lt;x, y, z>.</p>
+     *  <p>The rotation vector represents the orientation of the device as a combination of an
+     *  <i>angle</i> and an <i>axis</i>, in which the device has rotated through an angle &#952
+     *  around an axis &lt;x, y, z>.</p>
      *  <p>The three elements of the rotation vector are
      *  &lt;x*sin(&#952/2), y*sin(&#952/2), z*sin(&#952/2)>, such that the magnitude of the rotation
      *  vector is equal to sin(&#952/2), and the direction of the rotation vector is equal to the
diff --git a/android/hardware/SensorListener.java b/android/hardware/SensorListener.java
index c71e968..e2033b6 100644
--- a/android/hardware/SensorListener.java
+++ b/android/hardware/SensorListener.java
@@ -19,8 +19,8 @@
 /**
  * Used for receiving notifications from the SensorManager when
  * sensor values have changed.
- * 
- * @deprecated Use 
+ *
+ * @deprecated Use
  * {@link android.hardware.SensorEventListener SensorEventListener} instead.
  */
 @Deprecated
@@ -36,7 +36,7 @@
      * <p><u>Definition of the coordinate system used below.</u><p>
      * <p>The X axis refers to the screen's horizontal axis
      * (the small edge in portrait mode, the long edge in landscape mode) and
-     * points to the right. 
+     * points to the right.
      * <p>The Y axis refers to the screen's vertical axis and points towards
      * the top of the screen (the origin is in the lower-left corner).
      * <p>The Z axis points toward the sky when the device is lying on its back
@@ -44,18 +44,18 @@
      * <p> <b>IMPORTANT NOTE:</b> The axis <b><u>are swapped</u></b> when the
      * device's screen orientation changes. To access the unswapped values,
      * use indices 3, 4 and 5 in values[].
-     * 
+     *
      * <p>{@link android.hardware.SensorManager#SENSOR_ORIENTATION SENSOR_ORIENTATION},
      * {@link android.hardware.SensorManager#SENSOR_ORIENTATION_RAW SENSOR_ORIENTATION_RAW}:<p>
      *  All values are angles in degrees.
-     * 
+     *
      * <p>values[0]: Azimuth, rotation around the Z axis (0<=azimuth<360).
      * 0 = North, 90 = East, 180 = South, 270 = West
-     * 
+     *
      * <p>values[1]: Pitch, rotation around X axis (-180<=pitch<=180), with positive
      * values when the z-axis moves toward the y-axis.
      *
-     * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values 
+     * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values
      * when the z-axis moves toward the x-axis.
      *
      * <p>Note that this definition of yaw, pitch and roll is different from the
@@ -64,17 +64,17 @@
      *
      * <p>{@link android.hardware.SensorManager#SENSOR_ACCELEROMETER SENSOR_ACCELEROMETER}:<p>
      *  All values are in SI units (m/s^2) and measure contact forces.
-     *  
-     *  <p>values[0]: force applied by the device on the x-axis 
-     *  <p>values[1]: force applied by the device on the y-axis 
+     *
+     *  <p>values[0]: force applied by the device on the x-axis
+     *  <p>values[1]: force applied by the device on the y-axis
      *  <p>values[2]: force applied by the device on the z-axis
-     *  
+     *
      *  <p><u>Examples</u>:
      *    <li>When the device is pushed on its left side toward the right, the
      *    x acceleration value is negative (the device applies a reaction force
      *    to the push toward the left)</li>
-     *    
-     *    <li>When the device lies flat on a table, the acceleration value is 
+     *
+     *    <li>When the device lies flat on a table, the acceleration value is
      *    {@link android.hardware.SensorManager#STANDARD_GRAVITY -STANDARD_GRAVITY},
      *    which correspond to the force the device applies on the table in reaction
      *    to gravity.</li>
@@ -83,7 +83,7 @@
      *  All values are in micro-Tesla (uT) and measure the ambient magnetic
      *  field in the X, Y and -Z axis.
      *  <p><b><u>Note:</u></b> the magnetic field's Z axis is inverted.
-     *  
+     *
      * @param sensor The ID of the sensor being monitored
      * @param values The new values for the sensor.
      */
@@ -97,5 +97,5 @@
      * @param sensor The ID of the sensor being monitored
      * @param accuracy The new accuracy of this sensor.
      */
-    public void onAccuracyChanged(int sensor, int accuracy);    
+    public void onAccuracyChanged(int sensor, int accuracy);
 }
diff --git a/android/hardware/SensorManager.java b/android/hardware/SensorManager.java
index e1cd451..35aaf78 100644
--- a/android/hardware/SensorManager.java
+++ b/android/hardware/SensorManager.java
@@ -83,7 +83,7 @@
     /** @hide */
     protected static final String TAG = "SensorManager";
 
-    private static final float[] mTempMatrix = new float[16];
+    private static final float[] sTempMatrix = new float[16];
 
     // Cached lists of sensors by type.  Guarded by mSensorListByType.
     private final SparseArray<List<Sensor>> mSensorListByType =
@@ -188,7 +188,7 @@
      * @deprecated use {@link android.hardware.Sensor Sensor} instead.
      */
     @Deprecated
-    public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1);
+    public static final int SENSOR_MAX = ((SENSOR_ALL + 1) >> 1);
 
 
     /**
@@ -425,8 +425,9 @@
                 } else {
                     list = new ArrayList<Sensor>();
                     for (Sensor i : fullList) {
-                        if (i.getType() == type)
+                        if (i.getType() == type) {
                             list.add(i);
+                        }
                     }
                 }
                 list = Collections.unmodifiableList(list);
@@ -461,8 +462,9 @@
         } else {
             List<Sensor> list = new ArrayList();
             for (Sensor i : fullList) {
-                if (i.getType() == type)
+                if (i.getType() == type) {
                     list.add(i);
+                }
             }
             return Collections.unmodifiableList(list);
         }
@@ -490,10 +492,11 @@
         // For the following sensor types, return a wake-up sensor. These types are by default
         // defined as wake-up sensors. For the rest of the SDK defined sensor types return a
         // non_wake-up version.
-        if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||
-                type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||
-                type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||
-                type == Sensor.TYPE_WRIST_TILT_GESTURE || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
+        if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION
+                || type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE
+                || type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE
+                || type == Sensor.TYPE_WRIST_TILT_GESTURE
+                || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
             wakeUpSensor = true;
         }
 
@@ -509,12 +512,12 @@
      * <p>
      * For example,
      * <ul>
-     *     <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up accelerometer
-     *     sensor if it exists. </li>
-     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up proximity
-     *     sensor if it exists. </li>
-     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity sensor
-     *     which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
+     *     <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up
+     *     accelerometer sensor if it exists. </li>
+     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up
+     *     proximity sensor if it exists. </li>
+     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity
+     *     sensor which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
      * </ul>
      * </p>
      * <p class="note">
@@ -532,8 +535,9 @@
     public Sensor getDefaultSensor(int type, boolean wakeUp) {
         List<Sensor> l = getSensorList(type);
         for (Sensor sensor : l) {
-            if (sensor.isWakeUpSensor() == wakeUp)
+            if (sensor.isWakeUpSensor() == wakeUp) {
                 return sensor;
+            }
         }
         return null;
     }
@@ -842,8 +846,8 @@
      * @return <code>true</code> if the sensor is supported and successfully enabled.
      * @see #registerListener(SensorEventListener, Sensor, int, int)
      */
-    public boolean registerListener(SensorEventListener listener, Sensor sensor, int samplingPeriodUs,
-            int maxReportLatencyUs, Handler handler) {
+    public boolean registerListener(SensorEventListener listener, Sensor sensor,
+            int samplingPeriodUs, int maxReportLatencyUs, Handler handler) {
         int delayUs = getDelay(samplingPeriodUs);
         return registerListenerImpl(listener, sensor, delayUs, handler, maxReportLatencyUs, 0);
     }
@@ -953,7 +957,7 @@
      * Used for receiving notifications from the SensorManager when dynamic sensors are connected or
      * disconnected.
      */
-    public static abstract class DynamicSensorCallback {
+    public abstract static class DynamicSensorCallback {
         /**
          * Called when there is a dynamic sensor being connected to the system.
          *
@@ -1180,7 +1184,7 @@
         float Ay = gravity[1];
         float Az = gravity[2];
 
-        final float normsqA = (Ax*Ax + Ay*Ay + Az*Az);
+        final float normsqA = (Ax * Ax + Ay * Ay + Az * Az);
         final float g = 9.81f;
         final float freeFallGravitySquared = 0.01f * g * g;
         if (normsqA < freeFallGravitySquared) {
@@ -1191,10 +1195,10 @@
         final float Ex = geomagnetic[0];
         final float Ey = geomagnetic[1];
         final float Ez = geomagnetic[2];
-        float Hx = Ey*Az - Ez*Ay;
-        float Hy = Ez*Ax - Ex*Az;
-        float Hz = Ex*Ay - Ey*Ax;
-        final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
+        float Hx = Ey * Az - Ez * Ay;
+        float Hy = Ez * Ax - Ex * Az;
+        float Hz = Ex * Ay - Ey * Ax;
+        final float normH = (float) Math.sqrt(Hx * Hx + Hy * Hy + Hz * Hz);
 
         if (normH < 0.1f) {
             // device is close to free fall (or in space?), or close to
@@ -1205,13 +1209,13 @@
         Hx *= invH;
         Hy *= invH;
         Hz *= invH;
-        final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
+        final float invA = 1.0f / (float) Math.sqrt(Ax * Ax + Ay * Ay + Az * Az);
         Ax *= invA;
         Ay *= invA;
         Az *= invA;
-        final float Mx = Ay*Hz - Az*Hy;
-        final float My = Az*Hx - Ax*Hz;
-        final float Mz = Ax*Hy - Ay*Hx;
+        final float Mx = Ay * Hz - Az * Hy;
+        final float My = Az * Hx - Ax * Hz;
+        final float Mz = Ax * Hy - Ay * Hx;
         if (R != null) {
             if (R.length == 9) {
                 R[0] = Hx;     R[1] = Hy;     R[2] = Hz;
@@ -1228,17 +1232,17 @@
             // compute the inclination matrix by projecting the geomagnetic
             // vector onto the Z (gravity) and X (horizontal component
             // of geomagnetic vector) axes.
-            final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
-            final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
-            final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
+            final float invE = 1.0f / (float) Math.sqrt(Ex * Ex + Ey * Ey + Ez * Ez);
+            final float c = (Ex * Mx + Ey * My + Ez * Mz) * invE;
+            final float s = (Ex * Ax + Ey * Ay + Ez * Az) * invE;
             if (I.length == 9) {
                 I[0] = 1;     I[1] = 0;     I[2] = 0;
                 I[3] = 0;     I[4] = c;     I[5] = s;
-                I[6] = 0;     I[7] =-s;     I[8] = c;
+                I[6] = 0;     I[7] = -s;     I[8] = c;
             } else if (I.length == 16) {
                 I[0] = 1;     I[1] = 0;     I[2] = 0;
                 I[4] = 0;     I[5] = c;     I[6] = s;
-                I[8] = 0;     I[9] =-s;     I[10]= c;
+                I[8] = 0;     I[9] = -s;     I[10] = c;
                 I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
                 I[15] = 1;
             }
@@ -1262,9 +1266,9 @@
      */
     public static float getInclination(float[] I) {
         if (I.length == 9) {
-            return (float)Math.atan2(I[5], I[4]);
+            return (float) Math.atan2(I[5], I[4]);
         } else {
-            return (float)Math.atan2(I[6], I[5]);
+            return (float) Math.atan2(I[6], I[5]);
         }
     }
 
@@ -1343,17 +1347,16 @@
      * @see #getRotationMatrix(float[], float[], float[], float[])
      */
 
-    public static boolean remapCoordinateSystem(float[] inR, int X, int Y,
-            float[] outR)
-    {
+    public static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) {
         if (inR == outR) {
-            final float[] temp = mTempMatrix;
-            synchronized(temp) {
+            final float[] temp = sTempMatrix;
+            synchronized (temp) {
                 // we don't expect to have a lot of contention
                 if (remapCoordinateSystemImpl(inR, X, Y, temp)) {
                     final int size = outR.length;
-                    for (int i=0 ; i<size ; i++)
+                    for (int i = 0; i < size; i++) {
                         outR[i] = temp[i];
+                    }
                     return true;
                 }
             }
@@ -1361,9 +1364,7 @@
         return remapCoordinateSystemImpl(inR, X, Y, outR);
     }
 
-    private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y,
-            float[] outR)
-    {
+    private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y, float[] outR) {
         /*
          * X and Y define a rotation matrix 'r':
          *
@@ -1376,14 +1377,18 @@
          */
 
         final int length = outR.length;
-        if (inR.length != length)
+        if (inR.length != length) {
             return false;   // invalid parameter
-        if ((X & 0x7C)!=0 || (Y & 0x7C)!=0)
+        }
+        if ((X & 0x7C) != 0 || (Y & 0x7C) != 0) {
             return false;   // invalid parameter
-        if (((X & 0x3)==0) || ((Y & 0x3)==0))
+        }
+        if (((X & 0x3) == 0) || ((Y & 0x3) == 0)) {
             return false;   // no axis specified
-        if ((X & 0x3) == (Y & 0x3))
+        }
+        if ((X & 0x3) == (Y & 0x3)) {
             return false;   // same axis specified
+        }
 
         // Z is "the other" axis, its sign is either +/- sign(X)*sign(Y)
         // this can be calculated by exclusive-or'ing X and Y; except for
@@ -1391,28 +1396,29 @@
         int Z = X ^ Y;
 
         // extract the axis (remove the sign), offset in the range 0 to 2.
-        final int x = (X & 0x3)-1;
-        final int y = (Y & 0x3)-1;
-        final int z = (Z & 0x3)-1;
+        final int x = (X & 0x3) - 1;
+        final int y = (Y & 0x3) - 1;
+        final int z = (Z & 0x3) - 1;
 
         // compute the sign of Z (whether it needs to be inverted)
-        final int axis_y = (z+1)%3;
-        final int axis_z = (z+2)%3;
-        if (((x^axis_y)|(y^axis_z)) != 0)
+        final int axis_y = (z + 1) % 3;
+        final int axis_z = (z + 2) % 3;
+        if (((x ^ axis_y) | (y ^ axis_z)) != 0) {
             Z ^= 0x80;
+        }
 
-        final boolean sx = (X>=0x80);
-        final boolean sy = (Y>=0x80);
-        final boolean sz = (Z>=0x80);
+        final boolean sx = (X >= 0x80);
+        final boolean sy = (Y >= 0x80);
+        final boolean sz = (Z >= 0x80);
 
         // Perform R * r, in avoiding actual muls and adds.
-        final int rowLength = ((length==16)?4:3);
-        for (int j=0 ; j<3 ; j++) {
-            final int offset = j*rowLength;
-            for (int i=0 ; i<3 ; i++) {
-                if (x==i)   outR[offset+i] = sx ? -inR[offset+0] : inR[offset+0];
-                if (y==i)   outR[offset+i] = sy ? -inR[offset+1] : inR[offset+1];
-                if (z==i)   outR[offset+i] = sz ? -inR[offset+2] : inR[offset+2];
+        final int rowLength = ((length == 16) ? 4 : 3);
+        for (int j = 0; j < 3; j++) {
+            final int offset = j * rowLength;
+            for (int i = 0; i < 3; i++) {
+                if (x == i)   outR[offset + i] = sx ? -inR[offset + 0] : inR[offset + 0];
+                if (y == i)   outR[offset + i] = sy ? -inR[offset + 1] : inR[offset + 1];
+                if (z == i)   outR[offset + i] = sz ? -inR[offset + 2] : inR[offset + 2];
             }
         }
         if (length == 16) {
@@ -1466,7 +1472,7 @@
      * @see #getRotationMatrix(float[], float[], float[], float[])
      * @see GeomagneticField
      */
-    public static float[] getOrientation(float[] R, float values[]) {
+    public static float[] getOrientation(float[] R, float[] values) {
         /*
          * 4x4 (length=16) case:
          *   /  R[ 0]   R[ 1]   R[ 2]   0  \
@@ -1481,13 +1487,13 @@
          *
          */
         if (R.length == 9) {
-            values[0] = (float)Math.atan2(R[1], R[4]);
-            values[1] = (float)Math.asin(-R[7]);
-            values[2] = (float)Math.atan2(-R[6], R[8]);
+            values[0] = (float) Math.atan2(R[1], R[4]);
+            values[1] = (float) Math.asin(-R[7]);
+            values[2] = (float) Math.atan2(-R[6], R[8]);
         } else {
-            values[0] = (float)Math.atan2(R[1], R[5]);
-            values[1] = (float)Math.asin(-R[9]);
-            values[2] = (float)Math.atan2(-R[8], R[10]);
+            values[0] = (float) Math.atan2(R[1], R[5]);
+            values[1] = (float) Math.asin(-R[9]);
+            values[2] = (float) Math.atan2(-R[8], R[10]);
         }
 
         return values;
@@ -1524,7 +1530,7 @@
      */
     public static float getAltitude(float p0, float p) {
         final float coef = 1.0f / 5.255f;
-        return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef));
+        return 44330.0f * (1.0f - (float) Math.pow(p / p0, coef));
     }
 
     /** Helper function to compute the angle change between two rotation matrices.
@@ -1557,12 +1563,13 @@
      *        (in radians) is stored
      */
 
-    public static void getAngleChange( float[] angleChange, float[] R, float[] prevR) {
-        float rd1=0,rd4=0, rd6=0,rd7=0, rd8=0;
-        float ri0=0,ri1=0,ri2=0,ri3=0,ri4=0,ri5=0,ri6=0,ri7=0,ri8=0;
-        float pri0=0, pri1=0, pri2=0, pri3=0, pri4=0, pri5=0, pri6=0, pri7=0, pri8=0;
+    public static void getAngleChange(float[] angleChange, float[] R, float[] prevR) {
+        float rd1 = 0, rd4 = 0, rd6 = 0, rd7 = 0, rd8 = 0;
+        float ri0 = 0, ri1 = 0, ri2 = 0, ri3 = 0, ri4 = 0, ri5 = 0, ri6 = 0, ri7 = 0, ri8 = 0;
+        float pri0 = 0, pri1 = 0, pri2 = 0, pri3 = 0, pri4 = 0;
+        float pri5 = 0, pri6 = 0, pri7 = 0, pri8 = 0;
 
-        if(R.length == 9) {
+        if (R.length == 9) {
             ri0 = R[0];
             ri1 = R[1];
             ri2 = R[2];
@@ -1572,7 +1579,7 @@
             ri6 = R[6];
             ri7 = R[7];
             ri8 = R[8];
-        } else if(R.length == 16) {
+        } else if (R.length == 16) {
             ri0 = R[0];
             ri1 = R[1];
             ri2 = R[2];
@@ -1584,7 +1591,7 @@
             ri8 = R[10];
         }
 
-        if(prevR.length == 9) {
+        if (prevR.length == 9) {
             pri0 = prevR[0];
             pri1 = prevR[1];
             pri2 = prevR[2];
@@ -1594,7 +1601,7 @@
             pri6 = prevR[6];
             pri7 = prevR[7];
             pri8 = prevR[8];
-        } else if(prevR.length == 16) {
+        } else if (prevR.length == 16) {
             pri0 = prevR[0];
             pri1 = prevR[1];
             pri2 = prevR[2];
@@ -1615,9 +1622,9 @@
         rd7 = pri2 * ri1 + pri5 * ri4 + pri8 * ri7; //rd[2][1]
         rd8 = pri2 * ri2 + pri5 * ri5 + pri8 * ri8; //rd[2][2]
 
-        angleChange[0] = (float)Math.atan2(rd1, rd4);
-        angleChange[1] = (float)Math.asin(-rd7);
-        angleChange[2] = (float)Math.atan2(-rd6, rd8);
+        angleChange[0] = (float) Math.atan2(rd1, rd4);
+        angleChange[1] = (float) Math.asin(-rd7);
+        angleChange[2] = (float) Math.atan2(-rd6, rd8);
 
     }
 
@@ -1650,8 +1657,8 @@
         if (rotationVector.length >= 4) {
             q0 = rotationVector[3];
         } else {
-            q0 = 1 - q1*q1 - q2*q2 - q3*q3;
-            q0 = (q0 > 0) ? (float)Math.sqrt(q0) : 0;
+            q0 = 1 - q1 * q1 - q2 * q2 - q3 * q3;
+            q0 = (q0 > 0) ? (float) Math.sqrt(q0) : 0;
         }
 
         float sq_q1 = 2 * q1 * q1;
@@ -1664,7 +1671,7 @@
         float q2_q3 = 2 * q2 * q3;
         float q1_q0 = 2 * q1 * q0;
 
-        if(R.length == 9) {
+        if (R.length == 9) {
             R[0] = 1 - sq_q2 - sq_q3;
             R[1] = q1_q2 - q3_q0;
             R[2] = q1_q3 + q2_q0;
@@ -1707,8 +1714,8 @@
         if (rv.length >= 4) {
             Q[0] = rv[3];
         } else {
-            Q[0] = 1 - rv[0]*rv[0] - rv[1]*rv[1] - rv[2]*rv[2];
-            Q[0] = (Q[0] > 0) ? (float)Math.sqrt(Q[0]) : 0;
+            Q[0] = 1 - rv[0] * rv[0] - rv[1] * rv[1] - rv[2] * rv[2];
+            Q[0] = (Q[0] > 0) ? (float) Math.sqrt(Q[0]) : 0;
         }
         Q[1] = rv[0];
         Q[2] = rv[1];
@@ -1800,7 +1807,7 @@
      */
     @SystemApi
     public boolean initDataInjection(boolean enable) {
-          return initDataInjectionImpl(enable);
+        return initDataInjectionImpl(enable);
     }
 
     /**
@@ -1846,9 +1853,9 @@
         }
         int expectedNumValues = Sensor.getMaxLengthValuesArray(sensor, Build.VERSION_CODES.M);
         if (values.length != expectedNumValues) {
-            throw new  IllegalArgumentException ("Wrong number of values for sensor " +
-                    sensor.getName() + " actual=" + values.length + " expected=" +
-                                                  expectedNumValues);
+            throw new  IllegalArgumentException("Wrong number of values for sensor "
+                    + sensor.getName() + " actual=" + values.length + " expected="
+                    + expectedNumValues);
         }
         if (accuracy < SENSOR_STATUS_NO_CONTACT || accuracy > SENSOR_STATUS_ACCURACY_HIGH) {
             throw new IllegalArgumentException("Invalid sensor accuracy");
diff --git a/android/hardware/SystemSensorManager.java b/android/hardware/SystemSensorManager.java
index 607788d..1174cb6 100644
--- a/android/hardware/SystemSensorManager.java
+++ b/android/hardware/SystemSensorManager.java
@@ -28,10 +28,11 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
-import dalvik.system.CloseGuard;
 
 import com.android.internal.annotations.GuardedBy;
 
+import dalvik.system.CloseGuard;
+
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.lang.ref.WeakReference;
@@ -40,7 +41,6 @@
 import java.util.List;
 import java.util.Map;
 
-
 /**
  * Sensor manager implementation that communicates with the built-in
  * system sensors.
@@ -101,7 +101,7 @@
 
     /** {@hide} */
     public SystemSensorManager(Context context, Looper mainLooper) {
-        synchronized(sLock) {
+        synchronized (sLock) {
             if (!sNativeClassInited) {
                 sNativeClassInited = true;
                 nativeClassInit();
@@ -114,7 +114,7 @@
         mNativeInstance = nativeCreate(context.getOpPackageName());
 
         // initialize the sensor list
-        for (int index = 0;;++index) {
+        for (int index = 0;; ++index) {
             Sensor sensor = new Sensor();
             if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
             mFullSensorsList.add(sensor);
@@ -157,9 +157,9 @@
             return false;
         }
         if (mSensorListeners.size() >= MAX_LISTENER_COUNT) {
-            throw new IllegalStateException("register failed, " +
-                "the sensor listeners size has exceeded the maximum limit " +
-                MAX_LISTENER_COUNT);
+            throw new IllegalStateException("register failed, "
+                + "the sensor listeners size has exceeded the maximum limit "
+                + MAX_LISTENER_COUNT);
         }
 
         // Invariants to preserve:
@@ -170,9 +170,10 @@
             SensorEventQueue queue = mSensorListeners.get(listener);
             if (queue == null) {
                 Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
-                final String fullClassName = listener.getClass().getEnclosingClass() != null ?
-                    listener.getClass().getEnclosingClass().getName() :
-                    listener.getClass().getName();
+                final String fullClassName =
+                        listener.getClass().getEnclosingClass() != null
+                            ? listener.getClass().getEnclosingClass().getName()
+                            : listener.getClass().getName();
                 queue = new SensorEventQueue(listener, looper, this, fullClassName);
                 if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
                     queue.dispose();
@@ -221,17 +222,18 @@
         if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false;
 
         if (mTriggerListeners.size() >= MAX_LISTENER_COUNT) {
-            throw new IllegalStateException("request failed, " +
-                    "the trigger listeners size has exceeded the maximum limit " +
-                    MAX_LISTENER_COUNT);
+            throw new IllegalStateException("request failed, "
+                    + "the trigger listeners size has exceeded the maximum limit "
+                    + MAX_LISTENER_COUNT);
         }
 
         synchronized (mTriggerListeners) {
             TriggerEventQueue queue = mTriggerListeners.get(listener);
             if (queue == null) {
-                final String fullClassName = listener.getClass().getEnclosingClass() != null ?
-                    listener.getClass().getEnclosingClass().getName() :
-                    listener.getClass().getName();
+                final String fullClassName =
+                        listener.getClass().getEnclosingClass() != null
+                            ? listener.getClass().getEnclosingClass().getName()
+                            : listener.getClass().getName();
                 queue = new TriggerEventQueue(listener, mMainLooper, this, fullClassName);
                 if (!queue.addSensor(sensor, 0, 0)) {
                     queue.dispose();
@@ -336,27 +338,27 @@
         mHandleToSensor.remove(sensor.getHandle());
 
         if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
-            synchronized(mTriggerListeners) {
+            synchronized (mTriggerListeners) {
                 HashMap<TriggerEventListener, TriggerEventQueue> triggerListeners =
-                    new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
+                        new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
 
-                for (TriggerEventListener l: triggerListeners.keySet()) {
-                    if (DEBUG_DYNAMIC_SENSOR){
-                        Log.i(TAG, "removed trigger listener" + l.toString() +
-                                   " due to sensor disconnection");
+                for (TriggerEventListener l : triggerListeners.keySet()) {
+                    if (DEBUG_DYNAMIC_SENSOR) {
+                        Log.i(TAG, "removed trigger listener" + l.toString()
+                                + " due to sensor disconnection");
                     }
                     cancelTriggerSensorImpl(l, sensor, true);
                 }
             }
         } else {
-            synchronized(mSensorListeners) {
+            synchronized (mSensorListeners) {
                 HashMap<SensorEventListener, SensorEventQueue> sensorListeners =
-                    new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
+                        new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
 
                 for (SensorEventListener l: sensorListeners.keySet()) {
-                    if (DEBUG_DYNAMIC_SENSOR){
-                        Log.i(TAG, "removed event listener" + l.toString() +
-                                   " due to sensor disconnection");
+                    if (DEBUG_DYNAMIC_SENSOR) {
+                        Log.i(TAG, "removed event listener" + l.toString()
+                                + " due to sensor disconnection");
                     }
                     unregisterListenerImpl(l, sensor);
                 }
@@ -365,7 +367,7 @@
     }
 
     private void updateDynamicSensorList() {
-        synchronized(mFullDynamicSensorsList) {
+        synchronized (mFullDynamicSensorsList) {
             if (mDynamicSensorListDirty) {
                 List<Sensor> list = new ArrayList<>();
                 nativeGetDynamicSensors(mNativeInstance, list);
@@ -488,15 +490,15 @@
 
         int i = 0, j = 0;
         while (true) {
-            if (j < oldList.size() && ( i >= newList.size() ||
-                    newList.get(i).getHandle() > oldList.get(j).getHandle()) ) {
+            if (j < oldList.size() && (i >= newList.size()
+                    || newList.get(i).getHandle() > oldList.get(j).getHandle())) {
                 changed = true;
                 if (removed != null) {
                     removed.add(oldList.get(j));
                 }
                 ++j;
-            } else if (i < newList.size() && ( j >= oldList.size() ||
-                    newList.get(i).getHandle() < oldList.get(j).getHandle())) {
+            } else if (i < newList.size() && (j >= oldList.size()
+                    || newList.get(i).getHandle() < oldList.get(j).getHandle())) {
                 changed = true;
                 if (added != null) {
                     added.add(newList.get(i));
@@ -505,8 +507,8 @@
                     updated.add(newList.get(i));
                 }
                 ++i;
-            } else if (i < newList.size() && j < oldList.size() &&
-                    newList.get(i).getHandle() == oldList.get(j).getHandle()) {
+            } else if (i < newList.size() && j < oldList.size()
+                    && newList.get(i).getHandle() == oldList.get(j).getHandle()) {
                 if (updated != null) {
                     updated.add(oldList.get(j));
                 }
@@ -623,7 +625,7 @@
      * associated with any listener and there is one InjectEventQueue associated with a
      * SensorManager instance.
      */
-    private static abstract class BaseEventQueue {
+    private abstract static class BaseEventQueue {
         private static native long nativeInitBaseEventQueue(long nativeManager,
                 WeakReference<BaseEventQueue> eventQWeak, MessageQueue msgQ,
                 String packageName, int mode, String opPackageName);
@@ -633,9 +635,9 @@
         private static native void nativeDestroySensorEventQueue(long eventQ);
         private static native int nativeFlushSensor(long eventQ);
         private static native int nativeInjectSensorData(long eventQ, int handle,
-                float[] values,int accuracy, long timestamp);
+                float[] values, int accuracy, long timestamp);
 
-        private long nSensorEventQueue;
+        private long mNativeSensorEventQueue;
         private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
         protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
         private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -646,7 +648,7 @@
 
         BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
             if (packageName == null) packageName = "";
-            nSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
+            mNativeSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
                     new WeakReference<>(this), looper.getQueue(),
                     packageName, mode, manager.mContext.getOpPackageName());
             mCloseGuard.open("dispose");
@@ -668,17 +670,17 @@
             addSensorEvent(sensor);
             if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {
                 // Try continuous mode if batching fails.
-                if (maxBatchReportLatencyUs == 0 ||
-                    maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
-                  removeSensor(sensor, false);
-                  return false;
+                if (maxBatchReportLatencyUs == 0
+                        || maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
+                    removeSensor(sensor, false);
+                    return false;
                 }
             }
             return true;
         }
 
         public boolean removeAllSensors() {
-            for (int i=0 ; i<mActiveSensors.size(); i++) {
+            for (int i = 0; i < mActiveSensors.size(); i++) {
                 if (mActiveSensors.valueAt(i) == true) {
                     int handle = mActiveSensors.keyAt(i);
                     Sensor sensor = mManager.mHandleToSensor.get(handle);
@@ -706,8 +708,8 @@
         }
 
         public int flush() {
-            if (nSensorEventQueue == 0) throw new NullPointerException();
-            return nativeFlushSensor(nSensorEventQueue);
+            if (mNativeSensorEventQueue == 0) throw new NullPointerException();
+            return nativeFlushSensor(mNativeSensorEventQueue);
         }
 
         public boolean hasSensors() {
@@ -731,29 +733,30 @@
                 }
                 mCloseGuard.close();
             }
-            if (nSensorEventQueue != 0) {
-                nativeDestroySensorEventQueue(nSensorEventQueue);
-                nSensorEventQueue = 0;
+            if (mNativeSensorEventQueue != 0) {
+                nativeDestroySensorEventQueue(mNativeSensorEventQueue);
+                mNativeSensorEventQueue = 0;
             }
         }
 
         private int enableSensor(
                 Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
-            if (nSensorEventQueue == 0) throw new NullPointerException();
+            if (mNativeSensorEventQueue == 0) throw new NullPointerException();
             if (sensor == null) throw new NullPointerException();
-            return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs,
+            return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
                     maxBatchReportLatencyUs);
         }
 
         protected int injectSensorDataBase(int handle, float[] values, int accuracy,
                                            long timestamp) {
-            return nativeInjectSensorData(nSensorEventQueue, handle, values, accuracy, timestamp);
+            return nativeInjectSensorData(
+                    mNativeSensorEventQueue, handle, values, accuracy, timestamp);
         }
 
         private int disableSensor(Sensor sensor) {
-            if (nSensorEventQueue == 0) throw new NullPointerException();
+            if (mNativeSensorEventQueue == 0) throw new NullPointerException();
             if (sensor == null) throw new NullPointerException();
-            return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
+            return nativeDisableSensor(mNativeSensorEventQueue, sensor.getHandle());
         }
         protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
                 long timestamp);
@@ -840,7 +843,7 @@
                     // sensor disconnected
                     return;
                 }
-                ((SensorEventListener2)mListener).onFlushCompleted(sensor);
+                ((SensorEventListener2) mListener).onFlushCompleted(sensor);
             }
             return;
         }
@@ -858,7 +861,7 @@
                 }
                 SensorAdditionalInfo info =
                         new SensorAdditionalInfo(sensor, type, serial, intValues, floatValues);
-                ((SensorEventCallback)mListener).onSensorAdditionalInfo(info);
+                ((SensorEventCallback) mListener).onSensorAdditionalInfo(info);
             }
         }
     }
@@ -930,8 +933,8 @@
             super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
         }
 
-        int injectSensorData(int handle, float[] values,int accuracy, long timestamp) {
-             return injectSensorDataBase(handle, values, accuracy, timestamp);
+        int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
+            return injectSensorDataBase(handle, values, accuracy, timestamp);
         }
 
         @SuppressWarnings("unused")
@@ -959,6 +962,7 @@
         int handle = -1;
         if (parameter.sensor != null) handle = parameter.sensor.getHandle();
         return nativeSetOperationParameter(
-                mNativeInstance, handle, parameter.type, parameter.floatValues, parameter.intValues) == 0;
+                mNativeInstance, handle,
+                parameter.type, parameter.floatValues, parameter.intValues) == 0;
     }
 }
diff --git a/android/hardware/camera2/DngCreator.java b/android/hardware/camera2/DngCreator.java
index 1a51acd..cc484ea 100644
--- a/android/hardware/camera2/DngCreator.java
+++ b/android/hardware/camera2/DngCreator.java
@@ -18,7 +18,6 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.ImageFormat;
@@ -37,6 +36,7 @@
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
+import java.util.Locale;
 import java.util.TimeZone;
 
 /**
@@ -122,7 +122,7 @@
         // Create this fresh each time since the time zone may change while a long-running application
         // is active.
         final DateFormat dateTimeStampFormat =
-            new SimpleDateFormat(TIFF_DATETIME_FORMAT);
+                new SimpleDateFormat(TIFF_DATETIME_FORMAT, Locale.US);
         dateTimeStampFormat.setTimeZone(TimeZone.getDefault());
 
         // Format for metadata
@@ -472,7 +472,8 @@
 
     private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
     private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd HH:mm:ss";
-    private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+    private static final DateFormat sExifGPSDateStamp =
+            new SimpleDateFormat(GPS_DATE_FORMAT_STR, Locale.US);
     private final Calendar mGPSTimeStampCalendar = Calendar
             .getInstance(TimeZone.getTimeZone("UTC"));
 
diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java
index 6fbacaf..b2af44e 100644
--- a/android/hardware/display/DisplayManager.java
+++ b/android/hardware/display/DisplayManager.java
@@ -278,6 +278,19 @@
      */
     public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
 
+    /**
+     * Virtual display flag: Indicates that the contents will be destroyed once
+     * the display is removed.
+     *
+     * Public virtual displays without this flag will move their content to main display
+     * stack once they're removed. Private vistual displays will always destroy their
+     * content on removal even without this flag.
+     *
+     * @see #createVirtualDisplay
+     * @hide
+     */
+    public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
diff --git a/android/hardware/location/NanoAppInstanceInfo.java b/android/hardware/location/NanoAppInstanceInfo.java
index ac6d83f..2623830 100644
--- a/android/hardware/location/NanoAppInstanceInfo.java
+++ b/android/hardware/location/NanoAppInstanceInfo.java
@@ -287,8 +287,10 @@
         mPublisher = in.readString();
         mName = in.readString();
 
+        mHandle = in.readInt();
         mAppId = in.readLong();
         mAppVersion = in.readInt();
+        mContexthubId = in.readInt();
         mNeededReadMemBytes = in.readInt();
         mNeededWriteMemBytes = in.readInt();
         mNeededExecMemBytes = in.readInt();
@@ -309,6 +311,8 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(mPublisher);
         out.writeString(mName);
+
+        out.writeInt(mHandle);
         out.writeLong(mAppId);
         out.writeInt(mAppVersion);
         out.writeInt(mContexthubId);
diff --git a/android/media/AmrInputStream.java b/android/media/AmrInputStream.java
index fb91bbb..efaf224 100644
--- a/android/media/AmrInputStream.java
+++ b/android/media/AmrInputStream.java
@@ -25,12 +25,12 @@
 
 
 /**
- * AmrInputStream
+ * DO NOT USE
  * @hide
  */
 public final class AmrInputStream extends InputStream {
     private final static String TAG = "AmrInputStream";
-    
+
     // frame is 20 msec at 8.000 khz
     private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
 
@@ -51,10 +51,10 @@
     private byte[] mOneByte = new byte[1];
 
     /**
-     * Create a new AmrInputStream, which converts 16 bit PCM to AMR
-     * @param inputStream InputStream containing 16 bit PCM.
+     * DO NOT USE - use MediaCodec instead
      */
     public AmrInputStream(InputStream inputStream) {
+        Log.w(TAG, "@@@@ AmrInputStream is not a public API @@@@");
         mInputStream = inputStream;
 
         MediaFormat format  = new MediaFormat();
@@ -83,17 +83,26 @@
         mInfo = new BufferInfo();
     }
 
+    /**
+     * DO NOT USE
+     */
     @Override
     public int read() throws IOException {
         int rtn = read(mOneByte, 0, 1);
         return rtn == 1 ? (0xff & mOneByte[0]) : -1;
     }
 
+    /**
+     * DO NOT USE
+     */
     @Override
     public int read(byte[] b) throws IOException {
         return read(b, 0, b.length);
     }
 
+    /**
+     * DO NOT USE
+     */
     @Override
     public int read(byte[] b, int offset, int length) throws IOException {
         if (mCodec == null) {
@@ -131,19 +140,15 @@
                 }
             }
 
-            // now read encoded data from the encoder (blocking, since we just filled up the
-            // encoder's input with data it should be able to output at least one buffer)
-            while (true) {
-                int index = mCodec.dequeueOutputBuffer(mInfo, -1);
-                if (index >= 0) {
-                    mBufIn = mInfo.size;
-                    ByteBuffer out = mCodec.getOutputBuffer(index);
-                    out.get(mBuf, 0 /* offset */, mBufIn /* length */);
-                    mCodec.releaseOutputBuffer(index,  false /* render */);
-                    if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        mSawOutputEOS = true;
-                    }
-                    break;
+            // now read encoded data from the encoder
+            int index = mCodec.dequeueOutputBuffer(mInfo, 0);
+            if (index >= 0) {
+                mBufIn = mInfo.size;
+                ByteBuffer out = mCodec.getOutputBuffer(index);
+                out.get(mBuf, 0 /* offset */, mBufIn /* length */);
+                mCodec.releaseOutputBuffer(index,  false /* render */);
+                if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    mSawOutputEOS = true;
                 }
             }
         }
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 3b9a5de..26ead3d 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -19,12 +19,14 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.media.AudioAttributesProto;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -177,7 +179,7 @@
 
     /**
      * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
-     *            if applicable.
+     *            if applicable, as well as audioattributes.proto.
      */
 
     /**
@@ -850,6 +852,21 @@
     }
 
     /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(AudioAttributesProto.USAGE, mUsage);
+        proto.write(AudioAttributesProto.CONTENT_TYPE, mContentType);
+        proto.write(AudioAttributesProto.FLAGS, mFlags);
+        // mFormattedTags is never null due to assignment in Builder or unmarshalling.
+        for (String t : mFormattedTags.split(";")) {
+            t = t.trim();
+            if (t != "") {
+                proto.write(AudioAttributesProto.TAGS, t);
+            }
+        }
+        // TODO: is the data in mBundle useful for debugging?
+    }
+
+    /** @hide */
     public String usageToString() {
         return usageToString(mUsage);
     }
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index 186b265..dab7632 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -4119,7 +4119,15 @@
                         Log.w(TAG, "updateAudioPortCache: listAudioPatches failed");
                         return status;
                     }
-                } while (patchGeneration[0] != portGeneration[0]);
+                    // Loop until patch generation is the same as port generation unless audio ports
+                    // and audio patches are not null.
+                } while (patchGeneration[0] != portGeneration[0]
+                        && (ports == null || patches == null));
+                // If the patch generation doesn't equal port generation, return ERROR here in case
+                // of mismatch between audio ports and audio patches.
+                if (patchGeneration[0] != portGeneration[0]) {
+                    return ERROR;
+                }
 
                 for (int i = 0; i < newPatches.size(); i++) {
                     for (int j = 0; j < newPatches.get(i).sources().length; j++) {
diff --git a/android/media/AudioPortEventHandler.java b/android/media/AudioPortEventHandler.java
index c152245..ac3904a 100644
--- a/android/media/AudioPortEventHandler.java
+++ b/android/media/AudioPortEventHandler.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import java.util.ArrayList;
@@ -30,6 +31,7 @@
 
 class AudioPortEventHandler {
     private Handler mHandler;
+    private HandlerThread mHandlerThread;
     private final ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners =
             new ArrayList<AudioManager.OnAudioPortUpdateListener>();
 
@@ -40,6 +42,8 @@
     private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3;
     private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4;
 
+    private static final long RESCHEDULE_MESSAGE_DELAY_MS = 100;
+
     /**
      * Accessed by native methods: JNI Callback context.
      */
@@ -51,11 +55,12 @@
             if (mHandler != null) {
                 return;
             }
-            // find the looper for our new event handler
-            Looper looper = Looper.getMainLooper();
+            // create a new thread for our new event handler
+            mHandlerThread = new HandlerThread(TAG);
+            mHandlerThread.start();
 
-            if (looper != null) {
-                mHandler = new Handler(looper) {
+            if (mHandlerThread.getLooper() != null) {
+                mHandler = new Handler(mHandlerThread.getLooper()) {
                     @Override
                     public void handleMessage(Message msg) {
                         ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
@@ -86,6 +91,12 @@
                         if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
                             int status = AudioManager.updateAudioPortCache(ports, patches, null);
                             if (status != AudioManager.SUCCESS) {
+                                // Since audio ports and audio patches are not null, the return
+                                // value could be ERROR due to inconsistency between port generation
+                                // and patch generation. In this case, we need to reschedule the
+                                // message to make sure the native callback is done.
+                                sendMessageDelayed(obtainMessage(msg.what, msg.obj),
+                                        RESCHEDULE_MESSAGE_DELAY_MS);
                                 return;
                             }
                         }
@@ -132,6 +143,9 @@
     @Override
     protected void finalize() {
         native_finalize();
+        if (mHandlerThread.isAlive()) {
+            mHandlerThread.quit();
+        }
     }
     private native void native_finalize();
 
@@ -168,6 +182,10 @@
             Handler handler = eventHandler.handler();
             if (handler != null) {
                 Message m = handler.obtainMessage(what, arg1, arg2, obj);
+                if (what != AUDIOPORT_EVENT_NEW_LISTENER) {
+                    // Except AUDIOPORT_EVENT_NEW_LISTENER, we can only respect the last message.
+                    handler.removeMessages(what);
+                }
                 handler.sendMessage(m);
             }
         }
diff --git a/android/media/ExifInterface.java b/android/media/ExifInterface.java
index 1f5edfa..ba41a7b 100644
--- a/android/media/ExifInterface.java
+++ b/android/media/ExifInterface.java
@@ -2584,22 +2584,21 @@
                             ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
                 }
 
-                // Note that the rotation angle from MediaMetadataRetriever for heif images
-                // are CCW, while rotation in ExifInterface orientations are CW.
                 String rotation = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
                 if (rotation != null) {
                     int orientation = ExifInterface.ORIENTATION_NORMAL;
 
+                    // all rotation angles in CW
                     switch (Integer.parseInt(rotation)) {
                         case 90:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
+                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
                             break;
                         case 180:
                             orientation = ExifInterface.ORIENTATION_ROTATE_180;
                             break;
                         case 270:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
+                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
                             break;
                     }
 
diff --git a/android/media/MediaCodecInfo.java b/android/media/MediaCodecInfo.java
index f85925d..f41e33f 100644
--- a/android/media/MediaCodecInfo.java
+++ b/android/media/MediaCodecInfo.java
@@ -2749,8 +2749,8 @@
                 mQualityRange = Utils
                         .parseIntRange(info.getString("quality-range"), mQualityRange);
             }
-            if (info.containsKey("feature-bitrate-control")) {
-                for (String mode: info.getString("feature-bitrate-control").split(",")) {
+            if (info.containsKey("feature-bitrate-modes")) {
+                for (String mode: info.getString("feature-bitrate-modes").split(",")) {
                     mBitControl |= parseBitrateMode(mode);
                 }
             }
diff --git a/android/media/MediaRouter.java b/android/media/MediaRouter.java
index fe427a7..70ab863 100644
--- a/android/media/MediaRouter.java
+++ b/android/media/MediaRouter.java
@@ -184,13 +184,15 @@
 
         void updateAudioRoutes(AudioRoutesInfo newRoutes) {
             boolean audioRoutesChanged = false;
+            boolean forceUseDefaultRoute = false;
+
             if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
                 mCurAudioRoutesInfo.mainType = newRoutes.mainType;
                 int name;
-                if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
-                        || (newRoutes.mainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
+                if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
+                        || (newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_headphones;
-                } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+                } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
                 } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_hdmi;
@@ -201,11 +203,16 @@
                 }
                 mDefaultAudioVideo.mNameResId = name;
                 dispatchRouteChanged(mDefaultAudioVideo);
+
+                if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
+                        | AudioRoutesInfo.MAIN_HEADPHONES | AudioRoutesInfo.MAIN_USB)) != 0) {
+                    forceUseDefaultRoute = true;
+                }
                 audioRoutesChanged = true;
             }
 
-            final int mainType = mCurAudioRoutesInfo.mainType;
             if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+                forceUseDefaultRoute = false;
                 mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
                 if (mCurAudioRoutesInfo.bluetoothName != null) {
                     if (mBluetoothA2dpRoute == null) {
@@ -231,30 +238,21 @@
             }
 
             if (audioRoutesChanged) {
-                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, getDefaultSystemAudioRoute(), false);
                 Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
+                if (mSelectedRoute == null || mSelectedRoute == mDefaultAudioVideo
+                        || mSelectedRoute == mBluetoothA2dpRoute) {
+                    if (forceUseDefaultRoute || mBluetoothA2dpRoute == null) {
+                        selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
+                    } else {
+                        selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
+                    }
+                }
             }
         }
 
-        RouteInfo getDefaultSystemAudioRoute() {
-            boolean globalBluetoothA2doOn = false;
-            try {
-                globalBluetoothA2doOn = mMediaRouterService.isGlobalBluetoothA2doOn();
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to call isSystemBluetoothA2doOn.", ex);
-            }
-            return (globalBluetoothA2doOn && mBluetoothA2dpRoute != null)
-                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
-        }
-
-        RouteInfo getCurrentSystemAudioRoute() {
-            return (isBluetoothA2dpOn() && mBluetoothA2dpRoute != null)
-                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
-        }
-
         boolean isBluetoothA2dpOn() {
             try {
-                return mAudioService.isBluetoothA2dpOn();
+                return mBluetoothA2dpRoute != null && mAudioService.isBluetoothA2dpOn();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error querying Bluetooth A2DP state", e);
                 return false;
@@ -602,13 +600,20 @@
 
             @Override
             public void onRestoreRoute() {
-                // Skip restoring route if the selected route is not a system audio route, or
-                // MediaRouter is initializing.
-                if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
-                        || mSelectedRoute == null) {
-                    return;
-                }
-                mSelectedRoute.select();
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Skip restoring route if the selected route is not a system audio route,
+                        // MediaRouter is initializing, or mClient was changed.
+                        if (Client.this != mClient || mSelectedRoute == null
+                                || (mSelectedRoute != mDefaultAudioVideo
+                                        && mSelectedRoute != mBluetoothA2dpRoute)) {
+                            return;
+                        }
+                        Log.v(TAG, "onRestoreRoute() : route=" + mSelectedRoute);
+                        mSelectedRoute.select();
+                    }
+                });
             }
         }
     }
@@ -940,10 +945,12 @@
         Log.v(TAG, "Selecting route: " + route);
         assert(route != null);
         final RouteInfo oldRoute = sStatic.mSelectedRoute;
+        final RouteInfo currentSystemRoute = sStatic.isBluetoothA2dpOn()
+                ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo;
         boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
                 || oldRoute == sStatic.mBluetoothA2dpRoute);
         if (oldRoute == route
-                && (!wasDefaultOrBluetoothRoute || route == sStatic.getCurrentSystemAudioRoute())) {
+                && (!wasDefaultOrBluetoothRoute || route == currentSystemRoute)) {
             return;
         }
         if (!route.matchesTypes(types)) {
@@ -1014,8 +1021,7 @@
 
     static void selectDefaultRouteStatic() {
         // TODO: Be smarter about the route types here; this selects for all valid.
-        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
-                && sStatic.mBluetoothA2dpRoute != null && sStatic.isBluetoothA2dpOn()) {
+        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute && sStatic.isBluetoothA2dpOn()) {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
         } else {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
diff --git a/android/media/PlayerBase.java b/android/media/PlayerBase.java
index 4808d7a..09449a1 100644
--- a/android/media/PlayerBase.java
+++ b/android/media/PlayerBase.java
@@ -127,8 +127,9 @@
             Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
         }
         synchronized (mLock) {
+            boolean attributesChanged = (mAttributes != attr);
             mAttributes = attr;
-            updateAppOpsPlayAudio_sync();
+            updateAppOpsPlayAudio_sync(attributesChanged);
         }
     }
 
@@ -200,16 +201,13 @@
     }
 
     void baseSetVolume(float leftVolume, float rightVolume) {
-        final boolean hasAppOpsPlayAudio;
+        final boolean isRestricted;
         synchronized (mLock) {
             mLeftVolume = leftVolume;
             mRightVolume = rightVolume;
-            hasAppOpsPlayAudio = mHasAppOpsPlayAudio;
-            if (isRestricted_sync()) {
-                return;
-            }
+            isRestricted = isRestricted_sync();
         }
-        playerSetVolume(!hasAppOpsPlayAudio/*muting*/,
+        playerSetVolume(isRestricted/*muting*/,
                 leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
     }
 
@@ -250,7 +248,7 @@
 
     private void updateAppOpsPlayAudio() {
         synchronized (mLock) {
-            updateAppOpsPlayAudio_sync();
+            updateAppOpsPlayAudio_sync(false);
         }
     }
 
@@ -258,7 +256,7 @@
      * To be called whenever a condition that might affect audibility of this player is updated.
      * Must be called synchronized on mLock.
      */
-    void updateAppOpsPlayAudio_sync() {
+    void updateAppOpsPlayAudio_sync(boolean attributesChanged) {
         boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
         try {
             int mode = AppOpsManager.MODE_IGNORED;
@@ -275,9 +273,10 @@
         // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
         // volume used by the player
         try {
-            if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
+            if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio ||
+                    attributesChanged) {
                 getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio);
-                if (mHasAppOpsPlayAudio) {
+                if (!isRestricted_sync()) {
                     if (DEBUG_APP_OPS) {
                         Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
                                 + "/" + mRightVolume);
diff --git a/android/mtp/MtpDatabase.java b/android/mtp/MtpDatabase.java
index 80fd5c0..17b2326 100644
--- a/android/mtp/MtpDatabase.java
+++ b/android/mtp/MtpDatabase.java
@@ -845,6 +845,33 @@
         return MtpConstants.RESPONSE_OK;
     }
 
+    private int moveObject(int handle, int newParent, String newPath) {
+        String[] whereArgs = new String[] {  Integer.toString(handle) };
+
+        // do not allow renaming any of the special subdirectories
+        if (isStorageSubDirectory(newPath)) {
+            return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+        }
+
+        // update database
+        ContentValues values = new ContentValues();
+        values.put(Files.FileColumns.DATA, newPath);
+        values.put(Files.FileColumns.PARENT, newParent);
+        int updated = 0;
+        try {
+            // note - we are relying on a special case in MediaProvider.update() to update
+            // the paths for all children in the case where this is a directory.
+            updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
+        }
+        if (updated == 0) {
+            Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
+            return MtpConstants.RESPONSE_GENERAL_ERROR;
+        }
+        return MtpConstants.RESPONSE_OK;
+    }
+
     private int setObjectProperty(int handle, int property,
                             long intValue, String stringValue) {
         switch (property) {
diff --git a/android/multiuser/BenchmarkRunner.java b/android/multiuser/BenchmarkRunner.java
index c7bebf3..629e6f4 100644
--- a/android/multiuser/BenchmarkRunner.java
+++ b/android/multiuser/BenchmarkRunner.java
@@ -17,13 +17,16 @@
 
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
 
+import java.io.IOException;
 import java.util.ArrayList;
 
 // Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
 public class BenchmarkRunner {
 
-    private static long COOL_OFF_PERIOD_MS = 2000;
+    private static final long COOL_OFF_PERIOD_MS = 1000;
 
     private static final int NUM_ITERATIONS = 4;
 
@@ -70,9 +73,13 @@
     }
 
     private void prepareForNextRun() {
-        // TODO: Once http://b/63115387 is fixed, look into using "am wait-for-broadcast-idle"
-        // command instead of waiting for a fixed amount of time.
         SystemClock.sleep(COOL_OFF_PERIOD_MS);
+        try {
+            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                    .executeShellCommand("am wait-for-broadcast-idle");
+        } catch (IOException e) {
+            throw new IllegalStateException("Cannot execute shell command", e);
+        }
         mStartTimeNs = System.nanoTime();
         mPausedDurationNs = 0;
     }
diff --git a/android/net/ConnectivityManager.java b/android/net/ConnectivityManager.java
index 744ee8e..d7ecc81 100644
--- a/android/net/ConnectivityManager.java
+++ b/android/net/ConnectivityManager.java
@@ -2078,16 +2078,30 @@
      * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
      * due to device configuration.
      *
+     * <p>If this app does not have permission to use this API, it will always
+     * return false rather than throw an exception.</p>
+     *
+     * <p>If the device has a hotspot provisioning app, the caller is required to hold the
+     * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.</p>
+     *
+     * <p>Otherwise, this method requires the caller to hold the ability to modify system
+     * settings as determined by {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @return a boolean - {@code true} indicating Tethering is supported.
      *
      * {@hide}
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+    @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED,
+            android.Manifest.permission.WRITE_SETTINGS})
     public boolean isTetheringSupported() {
+        String pkgName = mContext.getOpPackageName();
         try {
-            String pkgName = mContext.getOpPackageName();
             return mService.isTetheringSupported(pkgName);
+        } catch (SecurityException e) {
+            // This API is not available to this caller, but for backward-compatibility
+            // this will just return false instead of throwing.
+            return false;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 5ae3400..79310e2 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -19,15 +19,16 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+
 import com.android.internal.util.HexDump;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 
 /**
  * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
  * RFC 4301.
- *
- * @hide
  */
 public final class IpSecAlgorithm implements Parcelable {
 
@@ -75,13 +76,7 @@
     public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
 
     /** @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})
     @Retention(RetentionPolicy.SOURCE)
     public @interface AlgorithmName {}
 
@@ -197,4 +192,12 @@
                 .append("}")
                 .toString();
     }
+
+    /** package */
+    static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
+        if (lhs == null || rhs == null) return (lhs == rhs);
+        return (lhs.mName.equals(rhs.mName)
+                && Arrays.equals(lhs.mKey, rhs.mKey)
+                && lhs.mTruncLenBits == rhs.mTruncLenBits);
+    }
 };
diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java
index 5a5c740..632b7fc 100644
--- a/android/net/IpSecConfig.java
+++ b/android/net/IpSecConfig.java
@@ -17,105 +17,170 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 /** @hide */
 public final class IpSecConfig implements Parcelable {
     private static final String TAG = "IpSecConfig";
 
-    //MODE_TRANSPORT or MODE_TUNNEL
-    int mode;
+    // MODE_TRANSPORT or MODE_TUNNEL
+    private int mMode = IpSecTransform.MODE_TRANSPORT;
 
-    // For tunnel mode
-    InetAddress localAddress;
+    // Needs to be valid only for tunnel mode
+    // Preventing this from being null simplifies Java->Native binder
+    private String mLocalAddress = "";
 
-    InetAddress remoteAddress;
+    // Preventing this from being null simplifies Java->Native binder
+    private String mRemoteAddress = "";
 
-    // Limit selection by network interface
-    Network network;
+    // The underlying Network that represents the "gateway" Network
+    // for outbound packets. It may also be used to select packets.
+    private Network mNetwork;
 
     public static class Flow {
         // Minimum requirements for identifying a transform
         // SPI identifying the IPsec flow in packet processing
         // and a remote IP address
-        int spiResourceId;
+        private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
 
         // Encryption Algorithm
-        IpSecAlgorithm encryption;
+        private IpSecAlgorithm mEncryption;
 
         // Authentication Algorithm
-        IpSecAlgorithm authentication;
+        private IpSecAlgorithm mAuthentication;
 
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("{spiResourceId=")
-                    .append(spiResourceId)
-                    .append(", encryption=")
-                    .append(encryption)
-                    .append(", authentication=")
-                    .append(authentication)
+                    .append("{mSpiResourceId=")
+                    .append(mSpiResourceId)
+                    .append(", mEncryption=")
+                    .append(mEncryption)
+                    .append(", mAuthentication=")
+                    .append(mAuthentication)
                     .append("}")
                     .toString();
         }
+
+        static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) {
+            if (lhs == null || rhs == null) return (lhs == rhs);
+            return (lhs.mSpiResourceId == rhs.mSpiResourceId
+                    && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
+                    && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
+        }
     }
 
-    final Flow[] flow = new Flow[] {new Flow(), new Flow()};
+    private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()};
 
     // For tunnel mode IPv4 UDP Encapsulation
     // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
-    int encapType;
-    int encapLocalPortResourceId;
-    int encapRemotePort;
+    private int mEncapType = IpSecTransform.ENCAP_NONE;
+    private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID;
+    private int mEncapRemotePort;
 
     // An interval, in seconds between the NattKeepalive packets
-    int nattKeepaliveInterval;
+    private int mNattKeepaliveInterval;
+
+    /** Set the mode for this IPsec transform */
+    public void setMode(int mode) {
+        mMode = mode;
+    }
+
+    /** Set the local IP address for Tunnel mode */
+    public void setLocalAddress(String localAddress) {
+        if (localAddress == null) {
+            throw new IllegalArgumentException("localAddress may not be null!");
+        }
+        mLocalAddress = localAddress;
+    }
+
+    /** Set the remote IP address for this IPsec transform */
+    public void setRemoteAddress(String remoteAddress) {
+        if (remoteAddress == null) {
+            throw new IllegalArgumentException("remoteAddress may not be null!");
+        }
+        mRemoteAddress = remoteAddress;
+    }
+
+    /** Set the SPI for a given direction by resource ID */
+    public void setSpiResourceId(int direction, int resourceId) {
+        mFlow[direction].mSpiResourceId = resourceId;
+    }
+
+    /** Set the encryption algorithm for a given direction */
+    public void setEncryption(int direction, IpSecAlgorithm encryption) {
+        mFlow[direction].mEncryption = encryption;
+    }
+
+    /** Set the authentication algorithm for a given direction */
+    public void setAuthentication(int direction, IpSecAlgorithm authentication) {
+        mFlow[direction].mAuthentication = authentication;
+    }
+
+    public void setNetwork(Network network) {
+        mNetwork = network;
+    }
+
+    public void setEncapType(int encapType) {
+        mEncapType = encapType;
+    }
+
+    public void setEncapSocketResourceId(int resourceId) {
+        mEncapSocketResourceId = resourceId;
+    }
+
+    public void setEncapRemotePort(int port) {
+        mEncapRemotePort = port;
+    }
+
+    public void setNattKeepaliveInterval(int interval) {
+        mNattKeepaliveInterval = interval;
+    }
 
     // Transport or Tunnel
     public int getMode() {
-        return mode;
+        return mMode;
     }
 
-    public InetAddress getLocalAddress() {
-        return localAddress;
+    public String getLocalAddress() {
+        return mLocalAddress;
     }
 
     public int getSpiResourceId(int direction) {
-        return flow[direction].spiResourceId;
+        return mFlow[direction].mSpiResourceId;
     }
 
-    public InetAddress getRemoteAddress() {
-        return remoteAddress;
+    public String getRemoteAddress() {
+        return mRemoteAddress;
     }
 
     public IpSecAlgorithm getEncryption(int direction) {
-        return flow[direction].encryption;
+        return mFlow[direction].mEncryption;
     }
 
     public IpSecAlgorithm getAuthentication(int direction) {
-        return flow[direction].authentication;
+        return mFlow[direction].mAuthentication;
     }
 
     public Network getNetwork() {
-        return network;
+        return mNetwork;
     }
 
     public int getEncapType() {
-        return encapType;
+        return mEncapType;
     }
 
-    public int getEncapLocalResourceId() {
-        return encapLocalPortResourceId;
+    public int getEncapSocketResourceId() {
+        return mEncapSocketResourceId;
     }
 
     public int getEncapRemotePort() {
-        return encapRemotePort;
+        return mEncapRemotePort;
     }
 
     public int getNattKeepaliveInterval() {
-        return nattKeepaliveInterval;
+        return mNattKeepaliveInterval;
     }
 
     // Parcelable Methods
@@ -127,82 +192,70 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        // TODO: Use a byte array or other better method for storing IPs that can also include scope
-        out.writeString((localAddress != null) ? localAddress.getHostAddress() : null);
-        // TODO: Use a byte array or other better method for storing IPs that can also include scope
-        out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
-        out.writeParcelable(network, flags);
-        out.writeInt(flow[IpSecTransform.DIRECTION_IN].spiResourceId);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags);
-        out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spiResourceId);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags);
-        out.writeInt(encapType);
-        out.writeInt(encapLocalPortResourceId);
-        out.writeInt(encapRemotePort);
+        out.writeInt(mMode);
+        out.writeString(mLocalAddress);
+        out.writeString(mRemoteAddress);
+        out.writeParcelable(mNetwork, flags);
+        out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, 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.writeInt(mEncapType);
+        out.writeInt(mEncapSocketResourceId);
+        out.writeInt(mEncapRemotePort);
+        out.writeInt(mNattKeepaliveInterval);
     }
 
-    // Package Private: Used by the IpSecTransform.Builder;
-    // there should be no public constructor for this object
-    IpSecConfig() {}
-
-    private static InetAddress readInetAddressFromParcel(Parcel in) {
-        String addrString = in.readString();
-        if (addrString == null) {
-            return null;
-        }
-        try {
-            return InetAddress.getByName(addrString);
-        } catch (UnknownHostException e) {
-            Log.wtf(TAG, "Invalid IpAddress " + addrString);
-            return null;
-        }
-    }
+    @VisibleForTesting
+    public IpSecConfig() {}
 
     private IpSecConfig(Parcel in) {
-        localAddress = readInetAddressFromParcel(in);
-        remoteAddress = readInetAddressFromParcel(in);
-        network = (Network) in.readParcelable(Network.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_IN].spiResourceId = in.readInt();
-        flow[IpSecTransform.DIRECTION_IN].encryption =
+        mMode = in.readInt();
+        mLocalAddress = in.readString();
+        mRemoteAddress = in.readString();
+        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+        mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt();
+        mFlow[IpSecTransform.DIRECTION_IN].mEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_IN].authentication =
+        mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_OUT].spiResourceId = in.readInt();
-        flow[IpSecTransform.DIRECTION_OUT].encryption =
+        mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
+        mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_OUT].authentication =
+        mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        encapType = in.readInt();
-        encapLocalPortResourceId = in.readInt();
-        encapRemotePort = in.readInt();
+        mEncapType = in.readInt();
+        mEncapSocketResourceId = in.readInt();
+        mEncapRemotePort = in.readInt();
+        mNattKeepaliveInterval = in.readInt();
     }
 
     @Override
     public String toString() {
         StringBuilder strBuilder = new StringBuilder();
         strBuilder
-                .append("{mode=")
-                .append(mode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
-                .append(", localAddress=")
-                .append(localAddress)
-                .append(", remoteAddress=")
-                .append(remoteAddress)
-                .append(", network=")
-                .append(network)
-                .append(", encapType=")
-                .append(encapType)
-                .append(", encapLocalPortResourceId=")
-                .append(encapLocalPortResourceId)
-                .append(", encapRemotePort=")
-                .append(encapRemotePort)
-                .append(", nattKeepaliveInterval=")
-                .append(nattKeepaliveInterval)
-                .append(", flow[OUT]=")
-                .append(flow[IpSecTransform.DIRECTION_OUT])
-                .append(", flow[IN]=")
-                .append(flow[IpSecTransform.DIRECTION_IN])
+                .append("{mMode=")
+                .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
+                .append(", mLocalAddress=")
+                .append(mLocalAddress)
+                .append(", mRemoteAddress=")
+                .append(mRemoteAddress)
+                .append(", mNetwork=")
+                .append(mNetwork)
+                .append(", mEncapType=")
+                .append(mEncapType)
+                .append(", mEncapSocketResourceId=")
+                .append(mEncapSocketResourceId)
+                .append(", mEncapRemotePort=")
+                .append(mEncapRemotePort)
+                .append(", mNattKeepaliveInterval=")
+                .append(mNattKeepaliveInterval)
+                .append(", mFlow[OUT]=")
+                .append(mFlow[IpSecTransform.DIRECTION_OUT])
+                .append(", mFlow[IN]=")
+                .append(mFlow[IpSecTransform.DIRECTION_IN])
                 .append("}");
 
         return strBuilder.toString();
@@ -218,4 +271,23 @@
                     return new IpSecConfig[size];
                 }
             };
+
+    @VisibleForTesting
+    /** Equals method used for testing */
+    public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
+        if (lhs == null || rhs == null) return (lhs == rhs);
+        return (lhs.mMode == rhs.mMode
+                && lhs.mLocalAddress.equals(rhs.mLocalAddress)
+                && lhs.mRemoteAddress.equals(rhs.mRemoteAddress)
+                && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
+                        || (lhs.mNetwork == rhs.mNetwork))
+                && lhs.mEncapType == rhs.mEncapType
+                && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
+                && lhs.mEncapRemotePort == rhs.mEncapRemotePort
+                && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT],
+                        rhs.mFlow[IpSecTransform.DIRECTION_OUT])
+                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN],
+                        rhs.mFlow[IpSecTransform.DIRECTION_IN]));
+    }
 }
diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java
index 2f791e1..d7b3256 100644
--- a/android/net/IpSecManager.java
+++ b/android/net/IpSecManager.java
@@ -25,7 +25,11 @@
 import android.os.RemoteException;
 import android.util.AndroidException;
 import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
 import dalvik.system.CloseGuard;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramSocket;
@@ -36,7 +40,9 @@
  * This class contains methods for managing IPsec sessions, which will perform kernel-space
  * encryption and decryption of socket or Network traffic.
  *
- * @hide
+ * <p>An IpSecManager may be obtained by calling {@link
+ * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
+ * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
  */
 @SystemService(Context.IPSEC_SERVICE)
 public final class IpSecManager {
@@ -184,7 +190,8 @@
         }
 
         /** @hide */
-        int getResourceId() {
+        @VisibleForTesting
+        public int getResourceId() {
             return mResourceId;
         }
     }
@@ -485,7 +492,8 @@
         }
 
         /** @hide */
-        int getResourceId() {
+        @VisibleForTesting
+        public int getResourceId() {
             return mResourceId;
         }
     };
diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java
index cfbac58..e15a2c6 100644
--- a/android/net/IpSecTransform.java
+++ b/android/net/IpSecTransform.java
@@ -26,9 +26,12 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+
 import dalvik.system.CloseGuard;
+
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -43,8 +46,6 @@
  *
  * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
  * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
- *
- * @hide
  */
 public final class IpSecTransform implements AutoCloseable {
     private static final String TAG = "IpSecTransform";
@@ -67,10 +68,10 @@
     public @interface TransformDirection {}
 
     /** @hide */
-    public static final int MODE_TUNNEL = 0;
+    public static final int MODE_TRANSPORT = 0;
 
     /** @hide */
-    public static final int MODE_TRANSPORT = 1;
+    public static final int MODE_TUNNEL = 1;
 
     /** @hide */
     public static final int ENCAP_NONE = 0;
@@ -112,7 +113,11 @@
         return IIpSecService.Stub.asInterface(b);
     }
 
-    private void checkResultStatusAndThrow(int status)
+    /**
+     * Checks the result status and throws an appropriate exception if
+     * the status is not Status.OK.
+     */
+    private void checkResultStatus(int status)
             throws IOException, IpSecManager.ResourceUnavailableException,
                     IpSecManager.SpiUnavailableException {
         switch (status) {
@@ -140,7 +145,7 @@
                 IpSecTransformResponse result =
                         svc.createTransportModeTransform(mConfig, new Binder());
                 int status = result.status;
-                checkResultStatusAndThrow(status);
+                checkResultStatus(status);
                 mResourceId = result.resourceId;
 
                 /* Keepalive will silently fail if not needed by the config; but, if needed and
@@ -242,61 +247,20 @@
 
     /* Package */
     void startKeepalive(Context c) {
-        // FIXME: NO_KEEPALIVE needs to be a constant
-        if (mConfig.getNattKeepaliveInterval() == 0) {
-            return;
-        }
-
-        ConnectivityManager cm =
-                (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
-
-        if (mKeepalive != null) {
-            Log.wtf(TAG, "Keepalive already started for this IpSecTransform.");
-            return;
-        }
-
-        synchronized (mKeepaliveSyncLock) {
-            mKeepalive =
-                    cm.startNattKeepalive(
-                            mConfig.getNetwork(),
-                            mConfig.getNattKeepaliveInterval(),
-                            mKeepaliveCallback,
-                            mConfig.getLocalAddress(),
-                            0x1234, /* FIXME: get the real port number again,
-                                    which we need to retrieve from the provided
-                                    EncapsulationSocket, and which isn't currently
-                                    stashed in IpSecConfig */
-                            mConfig.getRemoteAddress());
-            try {
-                // FIXME: this is still a horrible way to fudge the synchronous callback
-                mKeepaliveSyncLock.wait(2000);
-            } catch (InterruptedException e) {
-            }
-        }
-        if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) {
-            throw new UnsupportedOperationException("Packet Keepalive cannot be started");
+        if (mConfig.getNattKeepaliveInterval() != 0) {
+            Log.wtf(TAG, "Keepalive not yet supported.");
         }
     }
 
-    /* Package */
-    int getResourceId() {
+    /** @hide */
+    @VisibleForTesting
+    public int getResourceId() {
         return mResourceId;
     }
 
     /* Package */
     void stopKeepalive() {
-        if (mKeepalive == null) {
-            return;
-        }
-        mKeepalive.stop();
-        synchronized (mKeepaliveSyncLock) {
-            if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) {
-                try {
-                    mKeepaliveSyncLock.wait(2000);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
+        return;
     }
 
     /**
@@ -322,7 +286,7 @@
          */
         public IpSecTransform.Builder setEncryption(
                 @TransformDirection int direction, IpSecAlgorithm algo) {
-            mConfig.flow[direction].encryption = algo;
+            mConfig.setEncryption(direction, algo);
             return this;
         }
 
@@ -337,7 +301,7 @@
          */
         public IpSecTransform.Builder setAuthentication(
                 @TransformDirection int direction, IpSecAlgorithm algo) {
-            mConfig.flow[direction].authentication = algo;
+            mConfig.setAuthentication(direction, algo);
             return this;
         }
 
@@ -360,9 +324,7 @@
          */
         public IpSecTransform.Builder setSpi(
                 @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
-            // TODO: convert to using the resource Id of the SPI. Then build() can validate
-            // the owner in the IpSecService
-            mConfig.flow[direction].spiResourceId = spi.getResourceId();
+            mConfig.setSpiResourceId(direction, spi.getResourceId());
             return this;
         }
 
@@ -377,7 +339,7 @@
          */
         @SystemApi
         public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
-            mConfig.network = net;
+            mConfig.setNetwork(net);
             return this;
         }
 
@@ -394,10 +356,9 @@
          */
         public IpSecTransform.Builder setIpv4Encapsulation(
                 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
-            // TODO: check encap type is valid.
-            mConfig.encapType = ENCAP_ESPINUDP;
-            mConfig.encapLocalPortResourceId = localSocket.getResourceId();
-            mConfig.encapRemotePort = remotePort;
+            mConfig.setEncapType(ENCAP_ESPINUDP);
+            mConfig.setEncapSocketResourceId(localSocket.getResourceId());
+            mConfig.setEncapRemotePort(remotePort);
             return this;
         }
 
@@ -415,7 +376,7 @@
          */
         @SystemApi
         public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
-            mConfig.nattKeepaliveInterval = intervalSeconds;
+            mConfig.setNattKeepaliveInterval(intervalSeconds);
             return this;
         }
 
@@ -448,10 +409,8 @@
         public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
                 throws IpSecManager.ResourceUnavailableException,
                         IpSecManager.SpiUnavailableException, IOException {
-            //FIXME: argument validation here
-            //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
-            mConfig.mode = MODE_TRANSPORT;
-            mConfig.remoteAddress = remoteAddress;
+            mConfig.setMode(MODE_TRANSPORT);
+            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
             return new IpSecTransform(mContext, mConfig).activate();
         }
 
@@ -472,9 +431,9 @@
                 InetAddress localAddress, InetAddress remoteAddress) {
             //FIXME: argument validation here
             //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
-            mConfig.localAddress = localAddress;
-            mConfig.remoteAddress = remoteAddress;
-            mConfig.mode = MODE_TUNNEL;
+            mConfig.setLocalAddress(localAddress.getHostAddress());
+            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+            mConfig.setMode(MODE_TUNNEL);
             return new IpSecTransform(mContext, mConfig);
         }
 
@@ -488,14 +447,5 @@
             mContext = context;
             mConfig = new IpSecConfig();
         }
-
-        /**
-         * Return an {@link IpSecConfig} object for testing purposes.
-         * @hide
-         */
-        @VisibleForTesting
-        public IpSecConfig getIpSecConfig() {
-            return mConfig;
-        }
     }
 }
diff --git a/android/net/LocalServerSocket.java b/android/net/LocalServerSocket.java
index 3fcde33..d1f49d2 100644
--- a/android/net/LocalServerSocket.java
+++ b/android/net/LocalServerSocket.java
@@ -16,14 +16,15 @@
 
 package android.net;
 
-import java.io.IOException;
+import java.io.Closeable;
 import java.io.FileDescriptor;
+import java.io.IOException;
 
 /**
  * Non-standard class for creating an inbound UNIX-domain socket
  * in the Linux abstract namespace.
  */
-public class LocalServerSocket {
+public class LocalServerSocket implements Closeable {
     private final LocalSocketImpl impl;
     private final LocalSocketAddress localAddress;
 
@@ -106,7 +107,7 @@
      * 
      * @throws IOException
      */
-    public void close() throws IOException
+    @Override public void close() throws IOException
     {
         impl.close();
     }
diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java
index 884a8a7..0230f36 100644
--- a/android/net/ip/ConnectivityPacketTracker.java
+++ b/android/net/ip/ConnectivityPacketTracker.java
@@ -61,11 +61,11 @@
     private static final String MARK_STOP = "--- STOP ---";
 
     private final String mTag;
-    private final Handler mHandler;
     private final LocalLog mLog;
     private final BlockingSocketReader mPacketListener;
+    private boolean mRunning;
 
-    public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) {
+    public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) {
         final String ifname;
         final int ifindex;
         final byte[] hwaddr;
@@ -81,44 +81,40 @@
         }
 
         mTag = TAG + "." + ifname;
-        mHandler = new Handler();
         mLog = log;
-        mPacketListener = new PacketListener(ifindex, hwaddr, mtu);
+        mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
     }
 
     public void start() {
-        mLog.log(MARK_START);
+        mRunning = true;
         mPacketListener.start();
     }
 
     public void stop() {
         mPacketListener.stop();
-        mLog.log(MARK_STOP);
+        mRunning = false;
     }
 
     private final class PacketListener extends BlockingSocketReader {
         private final int mIfIndex;
         private final byte mHwAddr[];
 
-        PacketListener(int ifindex, byte[] hwaddr, int mtu) {
-            super(mtu);
+        PacketListener(Handler h, int ifindex, byte[] hwaddr, int mtu) {
+            super(h, mtu);
             mIfIndex = ifindex;
             mHwAddr = hwaddr;
         }
 
         @Override
-        protected FileDescriptor createSocket() {
+        protected FileDescriptor createFd() {
             FileDescriptor s = null;
             try {
-                // TODO: Evaluate switching to SOCK_DGRAM and changing the
-                // BlockingSocketReader's read() to recvfrom(), so that this
-                // might work on non-ethernet-like links (via SLL).
                 s = Os.socket(AF_PACKET, SOCK_RAW, 0);
                 NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
                 Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex));
             } catch (ErrnoException | IOException e) {
                 logError("Failed to create packet tracking socket: ", e);
-                closeSocket(s);
+                closeFd(s);
                 return null;
             }
             return s;
@@ -136,13 +132,27 @@
         }
 
         @Override
+        protected void onStart() {
+            mLog.log(MARK_START);
+        }
+
+        @Override
+        protected void onStop() {
+            if (mRunning) {
+                mLog.log(MARK_STOP);
+            } else {
+                mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)");
+            }
+        }
+
+        @Override
         protected void logError(String msg, Exception e) {
             Log.e(mTag, msg, e);
             addLogEntry(msg + e);
         }
 
         private void addLogEntry(String entry) {
-            mHandler.post(() -> mLog.log(entry));
+            mLog.log(entry);
         }
     }
 }
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index b1eb085..bc07b81 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -1515,7 +1515,8 @@
 
         private ConnectivityPacketTracker createPacketTracker() {
             try {
-                return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog);
+                return new ConnectivityPacketTracker(
+                        getHandler(), mNetworkInterface, mConnectivityPacketLog);
             } catch (IllegalArgumentException e) {
                 return null;
             }
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
index d520b97..97e83f9 100644
--- a/android/net/metrics/WakeupStats.java
+++ b/android/net/metrics/WakeupStats.java
@@ -35,7 +35,7 @@
     public long systemWakeups = 0;
     public long nonApplicationWakeups = 0;
     public long applicationWakeups = 0;
-    public long unroutedWakeups = 0;
+    public long noUidWakeups = 0;
     public long durationSec = 0;
 
     public WakeupStats(String iface) {
@@ -58,7 +58,7 @@
                 systemWakeups++;
                 break;
             case NO_UID:
-                unroutedWakeups++;
+                noUidWakeups++;
                 break;
             default:
                 if (ev.uid >= Process.FIRST_APPLICATION_UID) {
@@ -80,7 +80,7 @@
                 .append(", system: ").append(systemWakeups)
                 .append(", apps: ").append(applicationWakeups)
                 .append(", non-apps: ").append(nonApplicationWakeups)
-                .append(", unrouted: ").append(unroutedWakeups)
+                .append(", no uid: ").append(noUidWakeups)
                 .append(", ").append(durationSec).append("s)")
                 .toString();
     }
diff --git a/android/net/util/BlockingSocketReader.java b/android/net/util/BlockingSocketReader.java
index 12fa1e5..99bf469 100644
--- a/android/net/util/BlockingSocketReader.java
+++ b/android/net/util/BlockingSocketReader.java
@@ -16,81 +16,106 @@
 
 package android.net.util;
 
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+
 import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.os.MessageQueue.OnFileDescriptorEventListener;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 
-import libcore.io.IoBridge;
+import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
 import java.io.IOException;
 
 
 /**
- * A thread that reads from a socket and passes the received packets to a
- * subclass's handlePacket() method.  The packet receive buffer is recycled
- * on every read call, so subclasses should make any copies they would like
- * inside their handlePacket() implementation.
+ * This class encapsulates the mechanics of registering a file descriptor
+ * with a thread's Looper and handling read events (and errors).
  *
- * All public methods may be called from any thread.
+ * Subclasses MUST implement createFd() and SHOULD override handlePacket().
+
+ * Subclasses can expect a call life-cycle like the following:
+ *
+ *     [1] start() calls createFd() and (if all goes well) onStart()
+ *
+ *     [2] yield, waiting for read event or error notification:
+ *
+ *             [a] readPacket() && handlePacket()
+ *
+ *             [b] if (no error):
+ *                     goto 2
+ *                 else:
+ *                     goto 3
+ *
+ *     [3] stop() calls onStop() if not previously stopped
+ *
+ * The packet receive buffer is recycled on every read call, so subclasses
+ * should make any copies they would like inside their handlePacket()
+ * implementation.
+ *
+ * All public methods MUST only be called from the same thread with which
+ * the Handler constructor argument is associated.
+ *
+ * TODO: rename this class to something more correctly descriptive (something
+ * like [or less horrible than] FdReadEventsHandler?).
  *
  * @hide
  */
 public abstract class BlockingSocketReader {
+    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+    private static final int UNREGISTER_THIS_FD = 0;
+
     public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
 
+    private final Handler mHandler;
+    private final MessageQueue mQueue;
     private final byte[] mPacket;
-    private final Thread mThread;
-    private volatile FileDescriptor mSocket;
-    private volatile boolean mRunning;
-    private volatile long mPacketsReceived;
+    private FileDescriptor mFd;
+    private long mPacketsReceived;
 
-    // Make it slightly easier for subclasses to properly close a socket
-    // without having to know this incantation.
-    public static final void closeSocket(@Nullable FileDescriptor fd) {
-        try {
-            IoBridge.closeAndSignalBlockedThreads(fd);
-        } catch (IOException ignored) {}
+    protected static void closeFd(FileDescriptor fd) {
+        IoUtils.closeQuietly(fd);
     }
 
-    protected BlockingSocketReader() {
-        this(DEFAULT_RECV_BUF_SIZE);
+    protected BlockingSocketReader(Handler h) {
+        this(h, DEFAULT_RECV_BUF_SIZE);
     }
 
-    protected BlockingSocketReader(int recvbufsize) {
-        if (recvbufsize < DEFAULT_RECV_BUF_SIZE) {
-            recvbufsize = DEFAULT_RECV_BUF_SIZE;
+    protected BlockingSocketReader(Handler h, int recvbufsize) {
+        mHandler = h;
+        mQueue = mHandler.getLooper().getQueue();
+        mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
+    }
+
+    public final void start() {
+        if (onCorrectThread()) {
+            createAndRegisterFd();
+        } else {
+            mHandler.post(() -> {
+                logError("start() called from off-thread", null);
+                createAndRegisterFd();
+            });
         }
-        mPacket = new byte[recvbufsize];
-        mThread = new Thread(() -> { mainLoop(); });
-    }
-
-    public final boolean start() {
-        if (mSocket != null) return false;
-
-        try {
-            mSocket = createSocket();
-        } catch (Exception e) {
-            logError("Failed to create socket: ", e);
-            return false;
-        }
-
-        if (mSocket == null) return false;
-
-        mRunning = true;
-        mThread.start();
-        return true;
     }
 
     public final void stop() {
-        mRunning = false;
-        closeSocket(mSocket);
-        mSocket = null;
+        if (onCorrectThread()) {
+            unregisterAndDestroyFd();
+        } else {
+            mHandler.post(() -> {
+                logError("stop() called from off-thread", null);
+                unregisterAndDestroyFd();
+            });
+        }
     }
 
-    public final boolean isRunning() { return mRunning; }
+    public final int recvBufSize() { return mPacket.length; }
 
     public final long numPacketsReceived() { return mPacketsReceived; }
 
@@ -98,11 +123,21 @@
      * Subclasses MUST create the listening socket here, including setting
      * all desired socket options, interface or address/port binding, etc.
      */
-    protected abstract FileDescriptor createSocket();
+    protected abstract FileDescriptor createFd();
+
+    /**
+     * Subclasses MAY override this to change the default read() implementation
+     * in favour of, say, recvfrom().
+     *
+     * Implementations MUST return the bytes read or throw an Exception.
+     */
+    protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
+        return Os.read(fd, packetBuffer, 0, packetBuffer.length);
+    }
 
     /**
      * Called by the main loop for every packet.  Any desired copies of
-     * |recvbuf| should be made in here, and the underlying byte array is
+     * |recvbuf| should be made in here, as the underlying byte array is
      * reused across all reads.
      */
     protected void handlePacket(byte[] recvbuf, int length) {}
@@ -113,43 +148,102 @@
     protected void logError(String msg, Exception e) {}
 
     /**
-     * Called by the main loop just prior to exiting.
+     * Called by start(), if successful, just prior to returning.
      */
-    protected void onExit() {}
+    protected void onStart() {}
 
-    private final void mainLoop() {
+    /**
+     * Called by stop() just prior to returning.
+     */
+    protected void onStop() {}
+
+    private void createAndRegisterFd() {
+        if (mFd != null) return;
+
+        try {
+            mFd = createFd();
+            if (mFd != null) {
+                // Force the socket to be non-blocking.
+                IoUtils.setBlocking(mFd, false);
+            }
+        } catch (Exception e) {
+            logError("Failed to create socket: ", e);
+            closeFd(mFd);
+            mFd = null;
+            return;
+        }
+
+        if (mFd == null) return;
+
+        mQueue.addOnFileDescriptorEventListener(
+                mFd,
+                FD_EVENTS,
+                new OnFileDescriptorEventListener() {
+                    @Override
+                    public int onFileDescriptorEvents(FileDescriptor fd, int events) {
+                        // Always call handleInput() so read/recvfrom are given
+                        // a proper chance to encounter a meaningful errno and
+                        // perhaps log a useful error message.
+                        if (!isRunning() || !handleInput()) {
+                            unregisterAndDestroyFd();
+                            return UNREGISTER_THIS_FD;
+                        }
+                        return FD_EVENTS;
+                    }
+                });
+        onStart();
+    }
+
+    private boolean isRunning() { return (mFd != null) && mFd.valid(); }
+
+    // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
+    private boolean handleInput() {
         while (isRunning()) {
             final int bytesRead;
 
             try {
-                // Blocking read.
-                // TODO: See if this can be converted to recvfrom.
-                bytesRead = Os.read(mSocket, mPacket, 0, mPacket.length);
+                bytesRead = readPacket(mFd, mPacket);
                 if (bytesRead < 1) {
                     if (isRunning()) logError("Socket closed, exiting", null);
                     break;
                 }
                 mPacketsReceived++;
             } catch (ErrnoException e) {
-                if (e.errno != OsConstants.EINTR) {
-                    if (isRunning()) logError("read error: ", e);
+                if (e.errno == OsConstants.EAGAIN) {
+                    // We've read everything there is to read this time around.
+                    return true;
+                } else if (e.errno == OsConstants.EINTR) {
+                    continue;
+                } else {
+                    if (isRunning()) logError("readPacket error: ", e);
                     break;
                 }
-                continue;
-            } catch (IOException ioe) {
-                if (isRunning()) logError("read error: ", ioe);
-                continue;
+            } catch (Exception e) {
+                if (isRunning()) logError("readPacket error: ", e);
+                break;
             }
 
             try {
                 handlePacket(mPacket, bytesRead);
             } catch (Exception e) {
-                logError("Unexpected exception: ", e);
+                logError("handlePacket error: ", e);
                 break;
             }
         }
 
-        stop();
-        onExit();
+        return false;
+    }
+
+    private void unregisterAndDestroyFd() {
+        if (mFd == null) return;
+
+        mQueue.removeOnFileDescriptorEventListener(mFd);
+        closeFd(mFd);
+        mFd = null;
+        onStop();
+    }
+
+    private boolean onCorrectThread() {
+        return (mHandler.getLooper() == Looper.myLooper());
     }
 }
diff --git a/android/net/util/NetworkConstants.java b/android/net/util/NetworkConstants.java
index 6065268..5a3a8be 100644
--- a/android/net/util/NetworkConstants.java
+++ b/android/net/util/NetworkConstants.java
@@ -107,6 +107,20 @@
     public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
 
     /**
+     * ICMP common (v4/v6) constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc792
+     *     - https://tools.ietf.org/html/rfc4443
+     */
+    public static final int ICMP_HEADER_TYPE_OFFSET = 0;
+    public static final int ICMP_HEADER_CODE_OFFSET = 1;
+    public static final int ICMP_HEADER_CHECKSUM_OFFSET = 2;
+    public static final int ICMP_ECHO_IDENTIFIER_OFFSET = 4;
+    public static final int ICMP_ECHO_SEQUENCE_NUMBER_OFFSET = 6;
+    public static final int ICMP_ECHO_DATA_OFFSET = 8;
+
+    /**
      * ICMPv6 constants.
      *
      * See also:
diff --git a/android/net/wifi/WifiConfiguration.java b/android/net/wifi/WifiConfiguration.java
index a145327..6438631 100644
--- a/android/net/wifi/WifiConfiguration.java
+++ b/android/net/wifi/WifiConfiguration.java
@@ -790,6 +790,28 @@
 
     /**
      * @hide
+     * Returns true if this WiFi config is for an open network.
+     */
+    public boolean isOpenNetwork() {
+        final int cardinality = allowedKeyManagement.cardinality();
+        final boolean hasNoKeyMgmt = cardinality == 0
+                || (cardinality == 1 && allowedKeyManagement.get(KeyMgmt.NONE));
+
+        boolean hasNoWepKeys = true;
+        if (wepKeys != null) {
+            for (int i = 0; i < wepKeys.length; i++) {
+                if (wepKeys[i] != null) {
+                    hasNoWepKeys = false;
+                    break;
+                }
+            }
+        }
+
+        return hasNoKeyMgmt && hasNoWepKeys;
+    }
+
+    /**
+     * @hide
      * Setting this value will force scan results associated with this configuration to
      * be included in the bucket of networks that are externally scored.
      * If not set, associated scan results will be treated as legacy saved networks and
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index 9c8ea88..b08b4b7 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -967,8 +967,7 @@
      * <li>allowedGroupCiphers</li>
      * </ul>
      * @return a list of network configurations in the form of a list
-     * of {@link WifiConfiguration} objects. Upon failure to fetch or
-     * when Wi-Fi is turned off, it can be null.
+     * of {@link WifiConfiguration} objects.
      */
     public List<WifiConfiguration> getConfiguredNetworks() {
         try {
diff --git a/android/net/wifi/WifiScanner.java b/android/net/wifi/WifiScanner.java
index f47d5ca..e3752ac 100644
--- a/android/net/wifi/WifiScanner.java
+++ b/android/net/wifi/WifiScanner.java
@@ -1105,8 +1105,6 @@
     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
 
     /** @hide */
-    public static final int CMD_SCAN                        = BASE + 0;
-    /** @hide */
     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
     /** @hide */
     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
@@ -1115,20 +1113,10 @@
     /** @hide */
     public static final int CMD_SCAN_RESULT                 = BASE + 5;
     /** @hide */
-    public static final int CMD_AP_FOUND                    = BASE + 9;
-    /** @hide */
-    public static final int CMD_AP_LOST                     = BASE + 10;
-    /** @hide */
-    public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
-    /** @hide */
-    public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
-    /** @hide */
     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
     /** @hide */
     public static final int CMD_OP_FAILED                   = BASE + 18;
     /** @hide */
-    public static final int CMD_PERIOD_CHANGED              = BASE + 19;
-    /** @hide */
     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
     /** @hide */
     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
@@ -1359,25 +1347,6 @@
                     ScanResult result = (ScanResult) msg.obj;
                     ((ScanListener) listener).onFullResult(result);
                     return;
-                case CMD_PERIOD_CHANGED:
-                    ((ScanListener) listener).onPeriodChanged(msg.arg1);
-                    return;
-                case CMD_AP_FOUND:
-                    ((BssidListener) listener).onFound(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                    return;
-                case CMD_AP_LOST:
-                    ((BssidListener) listener).onLost(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                    return;
-                case CMD_WIFI_CHANGE_DETECTED:
-                    ((WifiChangeListener) listener).onChanging(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                   return;
-                case CMD_WIFI_CHANGES_STABILIZED:
-                    ((WifiChangeListener) listener).onQuiescence(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                    return;
                 case CMD_SINGLE_SCAN_COMPLETED:
                     if (DBG) Log.d(TAG, "removing listener for single scan");
                     removeListener(msg.arg2);
diff --git a/android/net/wifi/aware/DiscoverySession.java b/android/net/wifi/aware/DiscoverySession.java
index 357f76e..9f73622 100644
--- a/android/net/wifi/aware/DiscoverySession.java
+++ b/android/net/wifi/aware/DiscoverySession.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.NetworkSpecifier;
-import android.net.wifi.RttManager;
 import android.util.Log;
 
 import dalvik.system.CloseGuard;
@@ -224,37 +223,6 @@
     }
 
     /**
-     * Start a ranging operation with the specified peers. The peer IDs are obtained from an
-     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
-     * byte[], java.util.List)} or
-     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
-     * byte[])} operation - can
-     * only range devices which are part of an ongoing discovery session.
-     *
-     * @param params   RTT parameters - each corresponding to a specific peer ID (the array sizes
-     *                 must be identical). The
-     *                 {@link android.net.wifi.RttManager.RttParams#bssid} member must be set to
-     *                 a peer ID - not to a MAC address.
-     * @param listener The listener to receive the results of the ranging session.
-     * @hide
-     * [TODO: b/28847998 - track RTT API & visilibity]
-     */
-    public void startRanging(RttManager.RttParams[] params, RttManager.RttListener listener) {
-        if (mTerminated) {
-            Log.w(TAG, "startRanging: called on terminated session");
-            return;
-        }
-
-        WifiAwareManager mgr = mMgr.get();
-        if (mgr == null) {
-            Log.w(TAG, "startRanging: called post GC on WifiAwareManager");
-            return;
-        }
-
-        mgr.startRanging(mClientId, mSessionId, params, listener);
-    }
-
-    /**
      * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
      * an unencrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
diff --git a/android/net/wifi/aware/PeerHandle.java b/android/net/wifi/aware/PeerHandle.java
index cd45c52..1b0aba1 100644
--- a/android/net/wifi/aware/PeerHandle.java
+++ b/android/net/wifi/aware/PeerHandle.java
@@ -32,4 +32,24 @@
 
     /** @hide */
     public int peerId;
+
+    /** @hide RTT_API */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof PeerHandle)) {
+            return false;
+        }
+
+        return peerId == ((PeerHandle) o).peerId;
+    }
+
+    /** @hide RTT_API */
+    @Override
+    public int hashCode() {
+        return peerId;
+    }
 }
diff --git a/android/net/wifi/aware/WifiAwareManager.java b/android/net/wifi/aware/WifiAwareManager.java
index df0d9d2..ed6804d 100644
--- a/android/net/wifi/aware/WifiAwareManager.java
+++ b/android/net/wifi/aware/WifiAwareManager.java
@@ -26,7 +26,6 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
-import android.net.wifi.RttManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -35,9 +34,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
 
 import libcore.util.HexEncoding;
 
@@ -45,7 +41,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.nio.BufferOverflowException;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -172,9 +167,6 @@
 
     private final Object mLock = new Object(); // lock access to the following vars
 
-    @GuardedBy("mLock")
-    private SparseArray<RttManager.RttListener> mRangingListeners = new SparseArray<>();
-
     /** @hide */
     public WifiAwareManager(Context context, IWifiAwareManager service) {
         mContext = context;
@@ -401,27 +393,6 @@
     }
 
     /** @hide */
-    public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
-                             RttManager.RttListener listener) {
-        if (VDBG) {
-            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
-                    + "params=" + Arrays.toString(params) + ", listener=" + listener);
-        }
-
-        int rangingKey = 0;
-        try {
-            rangingKey = mService.startRanging(clientId, sessionId,
-                    new RttManager.ParcelableRttParams(params));
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        synchronized (mLock) {
-            mRangingListeners.put(rangingKey, listener);
-        }
-    }
-
-    /** @hide */
     public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
             PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
         if (VDBG) {
@@ -500,29 +471,12 @@
         private static final int CALLBACK_CONNECT_SUCCESS = 0;
         private static final int CALLBACK_CONNECT_FAIL = 1;
         private static final int CALLBACK_IDENTITY_CHANGED = 2;
-        private static final int CALLBACK_RANGING_SUCCESS = 3;
-        private static final int CALLBACK_RANGING_FAILURE = 4;
-        private static final int CALLBACK_RANGING_ABORTED = 5;
 
         private final Handler mHandler;
         private final WeakReference<WifiAwareManager> mAwareManager;
         private final Binder mBinder;
         private final Looper mLooper;
 
-        RttManager.RttListener getAndRemoveRangingListener(int rangingId) {
-            WifiAwareManager mgr = mAwareManager.get();
-            if (mgr == null) {
-                Log.w(TAG, "getAndRemoveRangingListener: called post GC");
-                return null;
-            }
-
-            synchronized (mgr.mLock) {
-                RttManager.RttListener listener = mgr.mRangingListeners.get(rangingId);
-                mgr.mRangingListeners.delete(rangingId);
-                return listener;
-            }
-        }
-
         /**
          * Constructs a {@link AttachCallback} using the specified looper.
          * All callbacks will delivered on the thread of the specified looper.
@@ -567,37 +521,6 @@
                                 identityChangedListener.onIdentityChanged((byte[]) msg.obj);
                             }
                             break;
-                        case CALLBACK_RANGING_SUCCESS: {
-                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
-                            if (listener == null) {
-                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
-                                        + ": no listener registered (anymore)");
-                            } else {
-                                listener.onSuccess(
-                                        ((RttManager.ParcelableRttResults) msg.obj).mResults);
-                            }
-                            break;
-                        }
-                        case CALLBACK_RANGING_FAILURE: {
-                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
-                            if (listener == null) {
-                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
-                                        + ": no listener registered (anymore)");
-                            } else {
-                                listener.onFailure(msg.arg2, (String) msg.obj);
-                            }
-                            break;
-                        }
-                        case CALLBACK_RANGING_ABORTED: {
-                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
-                            if (listener == null) {
-                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
-                                        + ": no listener registered (anymore)");
-                            } else {
-                                listener.onAborted();
-                            }
-                            break;
-                        }
                     }
                 }
             };
@@ -629,43 +552,6 @@
             msg.obj = mac;
             mHandler.sendMessage(msg);
         }
-
-        @Override
-        public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
-            if (VDBG) {
-                Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
-            }
-
-            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_SUCCESS);
-            msg.arg1 = rangingId;
-            msg.obj = results;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onRangingFailure(int rangingId, int reason, String description) {
-            if (VDBG) {
-                Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
-                        + ", description=" + description);
-            }
-
-            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_FAILURE);
-            msg.arg1 = rangingId;
-            msg.arg2 = reason;
-            msg.obj = description;
-            mHandler.sendMessage(msg);
-
-        }
-
-        @Override
-        public void onRangingAborted(int rangingId) {
-            if (VDBG) Log.v(TAG, "onRangingAborted: rangingId=" + rangingId);
-
-            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_ABORTED);
-            msg.arg1 = rangingId;
-            mHandler.sendMessage(msg);
-
-        }
     }
 
     private static class WifiAwareDiscoverySessionCallbackProxy extends
diff --git a/android/net/wifi/rtt/RangingRequest.java b/android/net/wifi/rtt/RangingRequest.java
new file mode 100644
index 0000000..997b680
--- /dev/null
+++ b/android/net/wifi/rtt/RangingRequest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.wifi.rtt;
+
+import android.net.wifi.ScanResult;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines the ranging request to other devices. The ranging request is built using
+ * {@link RangingRequest.Builder}.
+ * A ranging request is executed using
+ * {@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)}).
+ *
+ * @hide RTT_API
+ */
+public final class RangingRequest implements Parcelable {
+    private static final int MAX_PEERS = 10;
+
+    /**
+     * 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)}.
+     *
+     * @return Maximum number of peers.
+     */
+    public static int getMaxPeers() {
+        return MAX_PEERS;
+    }
+
+    /** @hide */
+    public final List<RttPeer> mRttPeers;
+
+    /** @hide */
+    private RangingRequest(List<RttPeer> rttPeers) {
+        mRttPeers = rttPeers;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeList(mRttPeers);
+    }
+
+    public static final Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
+        @Override
+        public RangingRequest[] newArray(int size) {
+            return new RangingRequest[size];
+        }
+
+        @Override
+        public RangingRequest createFromParcel(Parcel in) {
+            return new RangingRequest(in.readArrayList(null));
+        }
+    };
+
+    /** @hide */
+    @Override
+    public String toString() {
+        StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", ",");
+        for (RttPeer rp : mRttPeers) {
+            sj.add(rp.toString());
+        }
+        return sj.toString();
+    }
+
+    /** @hide */
+    public void enforceValidity() {
+        if (mRttPeers.size() > MAX_PEERS) {
+            throw new IllegalArgumentException(
+                    "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
+        }
+    }
+
+    /**
+     * Builder class used to construct {@link RangingRequest} objects.
+     */
+    public static final class Builder {
+        private List<RttPeer> mRttPeers = new ArrayList<>();
+
+        /**
+         * Add the device specified by the {@link ScanResult} to the list of devices with
+         * which to measure range. The total number of results added to a request cannot exceed the
+         * limit specified by {@link #getMaxPeers()}.
+         *
+         * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addAp(ScanResult apInfo) {
+            if (apInfo == null) {
+                throw new IllegalArgumentException("Null ScanResult!");
+            }
+            mRttPeers.add(new RttPeerAp(apInfo));
+            return this;
+        }
+
+        /**
+         * Add the devices specified by the {@link ScanResult}s to the list of devices with
+         * which to measure range. The total number of results added to a request cannot exceed the
+         * limit specified by {@link #getMaxPeers()}.
+         *
+         * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addAps(List<ScanResult> apInfos) {
+            if (apInfos == null) {
+                throw new IllegalArgumentException("Null list of ScanResults!");
+            }
+            for (ScanResult scanResult : apInfos) {
+                addAp(scanResult);
+            }
+            return this;
+        }
+
+        /**
+         * Build {@link RangingRequest} given the current configurations made on the
+         * builder.
+         */
+        public RangingRequest build() {
+            return new RangingRequest(mRttPeers);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof RangingRequest)) {
+            return false;
+        }
+
+        RangingRequest lhs = (RangingRequest) o;
+
+        return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers);
+    }
+
+    @Override
+    public int hashCode() {
+        return mRttPeers.hashCode();
+    }
+
+    /** @hide */
+    public interface RttPeer {
+        // empty (marker interface)
+    }
+
+    /** @hide */
+    public static class RttPeerAp implements RttPeer, Parcelable {
+        public final ScanResult scanResult;
+
+        public RttPeerAp(ScanResult scanResult) {
+            this.scanResult = scanResult;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            scanResult.writeToParcel(dest, flags);
+        }
+
+        public static final Creator<RttPeerAp> CREATOR = new Creator<RttPeerAp>() {
+            @Override
+            public RttPeerAp[] newArray(int size) {
+                return new RttPeerAp[size];
+            }
+
+            @Override
+            public RttPeerAp createFromParcel(Parcel in) {
+                return new RttPeerAp(ScanResult.CREATOR.createFromParcel(in));
+            }
+        };
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttPeerAp: scanResult=").append(
+                    scanResult.toString()).toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof RttPeerAp)) {
+                return false;
+            }
+
+            RttPeerAp lhs = (RttPeerAp) o;
+
+            // Note: the only thing which matters for the request identity is the BSSID of the AP
+            return TextUtils.equals(scanResult.BSSID, lhs.scanResult.BSSID);
+        }
+
+        @Override
+        public int hashCode() {
+            return scanResult.hashCode();
+        }
+    }
+}
\ No newline at end of file
diff --git a/android/net/wifi/rtt/RangingResult.java b/android/net/wifi/rtt/RangingResult.java
new file mode 100644
index 0000000..918803e
--- /dev/null
+++ b/android/net/wifi/rtt/RangingResult.java
@@ -0,0 +1,194 @@
+/*
+ * 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.wifi.rtt;
+
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Ranging result for a request started by
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. Results are
+ * returned in {@link RangingResultCallback#onRangingResults(List)}.
+ * <p>
+ * A ranging result is the distance measurement result for a single device specified in the
+ * {@link RangingRequest}.
+ *
+ * @hide RTT_API
+ */
+public final class RangingResult implements Parcelable {
+    private static final String TAG = "RangingResult";
+
+    private final int mStatus;
+    private final byte[] mMac;
+    private final int mDistanceCm;
+    private final int mDistanceStdDevCm;
+    private final int mRssi;
+    private final long mTimestamp;
+
+    /** @hide */
+    public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
+            long timestamp) {
+        mStatus = status;
+        mMac = mac;
+        mDistanceCm = distanceCm;
+        mDistanceStdDevCm = distanceStdDevCm;
+        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.
+     */
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * @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.
+     */
+    public byte[] getMacAddress() {
+        return mMac;
+    }
+
+    /**
+     * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     */
+    public int getDistanceCm() {
+        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+            Log.e(TAG, "getDistanceCm(): invalid value retrieved");
+        }
+        return mDistanceCm;
+    }
+
+    /**
+     * @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.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     */
+    public int getDistanceStdDevCm() {
+        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+            Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+        }
+        return mDistanceStdDevCm;
+    }
+
+    /**
+     * @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}.
+     */
+    public int getRssi() {
+        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+            // TODO: should this be an exception?
+            Log.e(TAG, "getRssi(): invalid value retrieved");
+        }
+        return mRssi;
+    }
+
+    /**
+     * @return The timestamp (in us) at which the ranging operation was performed
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     */
+    public long getRangingTimestamp() {
+        return mTimestamp;
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mStatus);
+        dest.writeByteArray(mMac);
+        dest.writeInt(mDistanceCm);
+        dest.writeInt(mDistanceStdDevCm);
+        dest.writeInt(mRssi);
+        dest.writeLong(mTimestamp);
+    }
+
+    /** @hide */
+    public static final Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
+        @Override
+        public RangingResult[] newArray(int size) {
+            return new RangingResult[size];
+        }
+
+        @Override
+        public RangingResult createFromParcel(Parcel in) {
+            int status = in.readInt();
+            byte[] mac = in.createByteArray();
+            int distanceCm = in.readInt();
+            int distanceStdDevCm = in.readInt();
+            int rssi = in.readInt();
+            long timestamp = in.readLong();
+            return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+        }
+    };
+
+    /** @hide */
+    @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(
+                mTimestamp).append("]").toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof RangingResult)) {
+            return false;
+        }
+
+        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;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+    }
+}
\ No newline at end of file
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
new file mode 100644
index 0000000..d7270ad
--- /dev/null
+++ b/android/net/wifi/rtt/RangingResultCallback.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.net.wifi.rtt;
+
+import android.os.Handler;
+
+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.
+ *
+ * @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;
+
+    /**
+     * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
+     * operation failed and the distance value is invalid.
+     */
+    public static final int STATUS_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.
+     */
+    public abstract void onRangingFailure();
+
+    /**
+     * Called when a ranging operation was executed. The list of results corresponds to devices
+     * specified in the ranging request.
+     *
+     * @param results List of range measurements, one per requested device.
+     */
+    public abstract void onRangingResults(List<RangingResult> results);
+}
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
new file mode 100644
index 0000000..a085de1
--- /dev/null
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -0,0 +1,100 @@
+package android.net.wifi.rtt;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This class provides the primary API for measuring distance (range) to other devices using the
+ * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology.
+ * <p>
+ * The devices which can be ranged include:
+ * <li>Access Points (APs)
+ * <p>
+ * Ranging requests are triggered using
+ * {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of
+ * successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
+ * callback.
+ *
+ * @hide RTT_API
+ */
+@SystemService(Context.WIFI_RTT2_SERVICE)
+public class WifiRttManager {
+    private static final String TAG = "WifiRttManager";
+    private static final boolean VDBG = true;
+
+    private final Context mContext;
+    private final IWifiRttManager mService;
+
+    /** @hide */
+    public WifiRttManager(Context context, IWifiRttManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+     * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+     *
+     * @param request  A request specifying a set of devices whose distance measurements are
+     *                 requested.
+     * @param callback A callback for the result of the ranging request.
+     * @param handler  The Handler on whose thread to execute the callbacks of the {@code
+     *                 callback} object. If a null is provided then the application's main thread
+     *                 will be used.
+     */
+    @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
+    public void startRanging(RangingRequest request, RangingResultCallback callback,
+            @Nullable Handler handler) {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler="
+                    + handler);
+        }
+
+        Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+        Binder binder = new Binder();
+        try {
+            mService.startRanging(binder, mContext.getOpPackageName(), request,
+                    new RttCallbackProxy(looper, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class RttCallbackProxy extends IRttCallback.Stub {
+        private final Handler mHandler;
+        private final RangingResultCallback mCallback;
+
+        RttCallbackProxy(Looper looper, RangingResultCallback callback) {
+            mHandler = new Handler(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
+            if (VDBG) {
+                Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
+                        + results);
+            }
+            mHandler.post(() -> {
+               if (status == RangingResultCallback.STATUS_SUCCESS) {
+                   mCallback.onRangingResults(results);
+               } else {
+                   mCallback.onRangingFailure();
+               }
+            });
+        }
+    }
+}
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 66b6b47..9881927 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -19,6 +19,7 @@
 import android.app.job.JobParameters;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
 import android.telephony.SignalStrength;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
@@ -29,11 +30,13 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -78,17 +81,17 @@
      * A constant indicating a sensor timer.
      */
     public static final int SENSOR = 3;
-    
+
     /**
      * A constant indicating a a wifi running timer
      */
     public static final int WIFI_RUNNING = 4;
-    
+
     /**
      * A constant indicating a full wifi lock timer
      */
     public static final int FULL_WIFI_LOCK = 5;
-    
+
     /**
      * A constant indicating a wifi scan
      */
@@ -190,7 +193,7 @@
     public static final int STATS_SINCE_UNPLUGGED = 2;
 
     // NOTE: Update this list if you add/change any stats above.
-    // These characters are supposed to represent "total", "last", "current", 
+    // These characters are supposed to represent "total", "last", "current",
     // and "unplugged". They were shortened for efficiency sake.
     private static final String[] STAT_NAMES = { "l", "c", "u" };
 
@@ -217,8 +220,10 @@
      *   - Package wakeup alarms are now on screen-off timebase
      * New in version 26:
      *   - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
+     * New in version 27:
+     *   - Always On Display (screen doze mode) time and power
      */
-    static final String CHECKIN_VERSION = "26";
+    static final int CHECKIN_VERSION = 27;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -1381,12 +1386,12 @@
         public static final int STATE_PHONE_SCANNING_FLAG = 1<<21;
         public static final int STATE_SCREEN_ON_FLAG = 1<<20;       // consider moving to states2
         public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; // consider moving to states2
-        // empty slot
+        public static final int STATE_SCREEN_DOZE_FLAG = 1 << 18;
         // empty slot
         public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<16;
 
         public static final int MOST_INTERESTING_STATES =
-            STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG;
+                STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG | STATE_SCREEN_DOZE_FLAG;
 
         public static final int SETTLE_TO_ZERO_STATES = 0xffff0000 & ~MOST_INTERESTING_STATES;
 
@@ -1414,8 +1419,8 @@
         public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20;
 
         public static final int MOST_INTERESTING_STATES2 =
-            STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
-            | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
+                STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
+                | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
 
         public static final int SETTLE_TO_ZERO_STATES2 = 0xffff0000 & ~MOST_INTERESTING_STATES2;
 
@@ -1863,6 +1868,21 @@
      */
     public abstract int getScreenOnCount(int which);
 
+    /**
+     * Returns the time in microseconds that the screen has been dozing while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getScreenDozeTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times the screen was turned dozing.
+     *
+     * {@hide}
+     */
+    public abstract int getScreenDozeCount(int which);
+
     public abstract long getInteractiveTime(long elapsedRealtimeUs, int which);
 
     public static final int SCREEN_BRIGHTNESS_DARK = 0;
@@ -2116,8 +2136,7 @@
         "group", "compl", "dorm", "uninit"
     };
 
-    public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS
-            = new BitDescription[] {
+    public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
         new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
         new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
         new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor", "s"),
@@ -2131,6 +2150,7 @@
         new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"),
         new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
         new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
+        new BitDescription(HistoryItem.STATE_SCREEN_DOZE_FLAG, "screen_doze", "Sd"),
         new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
                 HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
                 DATA_CONNECTION_NAMES, DATA_CONNECTION_NAMES),
@@ -2467,6 +2487,18 @@
     public abstract int getDischargeAmountScreenOffSinceCharge();
 
     /**
+     * Get the amount the battery has discharged while the screen was doze,
+     * since the last time power was unplugged.
+     */
+    public abstract int getDischargeAmountScreenDoze();
+
+    /**
+     * Get the amount the battery has discharged while the screen was doze,
+     * since the last time the device was charged.
+     */
+    public abstract int getDischargeAmountScreenDozeSinceCharge();
+
+    /**
      * Returns the total, last, or current battery uptime in microseconds.
      *
      * @param curTime the elapsed realtime in microseconds.
@@ -2483,7 +2515,7 @@
     public abstract long computeBatteryRealtime(long curTime, int which);
 
     /**
-     * Returns the total, last, or current battery screen off uptime in microseconds.
+     * Returns the total, last, or current battery screen off/doze uptime in microseconds.
      *
      * @param curTime the elapsed realtime in microseconds.
      * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -2491,7 +2523,7 @@
     public abstract long computeBatteryScreenOffUptime(long curTime, int which);
 
     /**
-     * Returns the total, last, or current battery screen off realtime in microseconds.
+     * Returns the total, last, or current battery screen off/doze realtime in microseconds.
      *
      * @param curTime the current elapsed realtime in microseconds.
      * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -2590,18 +2622,24 @@
     };
 
     /**
-     * Return the counter keeping track of the amount of battery discharge while the screen was off,
-     * measured in micro-Ampere-hours. This will be non-zero only if the device's battery has
-     * a coulomb counter.
-     */
-    public abstract LongCounter getDischargeScreenOffCoulombCounter();
-
-    /**
-     * Return the counter keeping track of the amount of battery discharge measured in
+     * Return the amount of battery discharge while the screen was off, measured in
      * micro-Ampere-hours. This will be non-zero only if the device's battery has
      * a coulomb counter.
      */
-    public abstract LongCounter getDischargeCoulombCounter();
+    public abstract long getMahDischargeScreenOff(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);
+
+    /**
+     * 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);
 
     /**
      * Returns the estimated real battery capacity, which may be less than the capacity
@@ -2953,6 +2991,31 @@
     }
 
     /**
+     * Dump a given timer stat to the proto stream.
+     *
+     * @param proto the ProtoOutputStream to log to
+     * @param fieldId type of data, the field to save to (e.g. AggregatedBatteryStats.WAKELOCK)
+     * @param timer a {@link Timer} to dump stats for
+     * @param rawRealtimeUs the current elapsed realtime of the system in microseconds
+     * @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) {
+        if (timer == null) {
+            return;
+        }
+        // Convert from microseconds to milliseconds with rounding
+        final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+        final int count = timer.getCountLocked(which);
+        if (totalTimeMs != 0 || count != 0) {
+            final long token = proto.start(fieldId);
+            proto.write(TimerProto.DURATION_MS, totalTimeMs);
+            proto.write(TimerProto.COUNT, count);
+            proto.end(token);
+        }
+    }
+
+    /**
      * Checks if the ControllerActivityCounter has any data worth dumping.
      */
     private static boolean controllerActivityHasData(ControllerActivityCounter counter, int which) {
@@ -3004,6 +3067,38 @@
         pw.println();
     }
 
+    /**
+     * Dumps the ControllerActivityCounter if it has any data worth dumping.
+     */
+    private static void dumpControllerActivityProto(ProtoOutputStream proto, long fieldId,
+                                                    ControllerActivityCounter counter,
+                                                    int which) {
+        if (!controllerActivityHasData(counter, which)) {
+            return;
+        }
+
+        final long cToken = proto.start(fieldId);
+
+        proto.write(ControllerActivityProto.IDLE_DURATION_MS,
+                counter.getIdleTimeCounter().getCountLocked(which));
+        proto.write(ControllerActivityProto.RX_DURATION_MS,
+                counter.getRxTimeCounter().getCountLocked(which));
+        proto.write(ControllerActivityProto.POWER_MAH,
+                counter.getPowerCounter().getCountLocked(which) / (1000 * 60 * 60));
+
+        long tToken;
+        LongCounter[] txCounters = counter.getTxTimeCounters();
+        for (int i = 0; i < txCounters.length; ++i) {
+            LongCounter c = txCounters[i];
+            tToken = proto.start(ControllerActivityProto.TX);
+            proto.write(ControllerActivityProto.TxLevel.LEVEL, i);
+            proto.write(ControllerActivityProto.TxLevel.DURATION_MS, c.getCountLocked(which));
+            proto.end(tToken);
+        }
+
+        proto.end(cToken);
+    }
+
     private final void printControllerActivityIfInteresting(PrintWriter pw, StringBuilder sb,
                                                             String prefix, String controllerName,
                                                             ControllerActivityCounter counter,
@@ -3112,6 +3207,7 @@
         final long totalRealtime = computeRealtime(rawRealtime, which);
         final long totalUptime = computeUptime(rawUptime, which);
         final long screenOnTime = getScreenOnTime(rawRealtime, which);
+        final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
         final long interactiveTime = getInteractiveTime(rawRealtime, which);
         final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
         final long deviceIdleModeLightTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT,
@@ -3124,9 +3220,9 @@
                 rawRealtime, which);
         final int connChanges = getNumConnectivityChange(which);
         final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
-        final long dischargeCount = getDischargeCoulombCounter().getCountLocked(which);
-        final long dischargeScreenOffCount = getDischargeScreenOffCoulombCounter()
-                .getCountLocked(which);
+        final long dischargeCount = getMahDischarge(which);
+        final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
+        final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
 
         final StringBuilder sb = new StringBuilder(128);
 
@@ -3143,7 +3239,8 @@
                 getStartClockTime(),
                 whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000,
                 getEstimatedBatteryCapacity(),
-                getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity());
+                getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity(),
+                screenDozeTime / 1000);
 
 
         // Calculate wakelock times across all uids.
@@ -3295,13 +3392,15 @@
                     getDischargeStartLevel()-getDischargeCurrentLevel(),
                     getDischargeStartLevel()-getDischargeCurrentLevel(),
                     getDischargeAmountScreenOn(), getDischargeAmountScreenOff(),
-                    dischargeCount / 1000, dischargeScreenOffCount / 1000);
+                    dischargeCount / 1000, dischargeScreenOffCount / 1000,
+                    getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000);
         } else {
             dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
                     getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
                     getDischargeAmountScreenOnSinceCharge(),
                     getDischargeAmountScreenOffSinceCharge(),
-                    dischargeCount / 1000, dischargeScreenOffCount / 1000);
+                    dischargeCount / 1000, dischargeScreenOffCount / 1000,
+                    getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000);
         }
 
         if (reqUid < 0) {
@@ -3831,6 +3930,7 @@
                 which);
         final long batteryTimeRemaining = computeBatteryTimeRemaining(rawRealtime);
         final long chargeTimeRemaining = computeChargeTimeRemaining(rawRealtime);
+        final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
 
         final StringBuilder sb = new StringBuilder(128);
 
@@ -3868,25 +3968,35 @@
 
         sb.setLength(0);
         sb.append(prefix);
-                sb.append("  Time on battery: ");
-                formatTimeMs(sb, whichBatteryRealtime / 1000); sb.append("(");
-                sb.append(formatRatioLocked(whichBatteryRealtime, totalRealtime));
-                sb.append(") realtime, ");
-                formatTimeMs(sb, whichBatteryUptime / 1000);
-                sb.append("("); sb.append(formatRatioLocked(whichBatteryUptime, totalRealtime));
-                sb.append(") uptime");
+        sb.append("  Time on battery: ");
+        formatTimeMs(sb, whichBatteryRealtime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryRealtime, totalRealtime));
+        sb.append(") realtime, ");
+        formatTimeMs(sb, whichBatteryUptime / 1000);
+        sb.append("("); sb.append(formatRatioLocked(whichBatteryUptime, whichBatteryRealtime));
+        sb.append(") uptime");
         pw.println(sb.toString());
+
         sb.setLength(0);
         sb.append(prefix);
-                sb.append("  Time on battery screen off: ");
-                formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
-                sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, totalRealtime));
-                sb.append(") realtime, ");
-                formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
-                sb.append("(");
-                sb.append(formatRatioLocked(whichBatteryScreenOffUptime, totalRealtime));
-                sb.append(") uptime");
+        sb.append("  Time on battery screen off: ");
+        formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, whichBatteryRealtime));
+        sb.append(") realtime, ");
+        formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
+        sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryScreenOffUptime, whichBatteryRealtime));
+        sb.append(") uptime");
         pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Time on battery screen doze: ");
+        formatTimeMs(sb, screenDozeTime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(screenDozeTime, whichBatteryRealtime));
+        sb.append(")");
+        pw.println(sb.toString());
+
         sb.setLength(0);
         sb.append(prefix);
                 sb.append("  Total run time: ");
@@ -3910,8 +4020,7 @@
             pw.println(sb.toString());
         }
 
-        final LongCounter dischargeCounter = getDischargeCoulombCounter();
-        final long dischargeCount = dischargeCounter.getCountLocked(which);
+        final long dischargeCount = getMahDischarge(which);
         if (dischargeCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -3921,8 +4030,7 @@
             pw.println(sb.toString());
         }
 
-        final LongCounter dischargeScreenOffCounter = getDischargeScreenOffCoulombCounter();
-        final long dischargeScreenOffCount = dischargeScreenOffCounter.getCountLocked(which);
+        final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
         if (dischargeScreenOffCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -3932,7 +4040,18 @@
             pw.println(sb.toString());
         }
 
-        final long dischargeScreenOnCount = dischargeCount - dischargeScreenOffCount;
+        final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+        if (dischargeScreenDozeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Screen doze discharge: ");
+            sb.append(BatteryStatsHelper.makemAh(dischargeScreenDozeCount / 1000.0));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeScreenOnCount =
+                dischargeCount - dischargeScreenOffCount - dischargeScreenDozeCount;
         if (dischargeScreenOnCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -4340,20 +4459,24 @@
                         pw.println(getDischargeCurrentLevel());
             }
             pw.print(prefix); pw.print("    Amount discharged while screen on: ");
-                    pw.println(getDischargeAmountScreenOn());
+            pw.println(getDischargeAmountScreenOn());
             pw.print(prefix); pw.print("    Amount discharged while screen off: ");
-                    pw.println(getDischargeAmountScreenOff());
+            pw.println(getDischargeAmountScreenOff());
+            pw.print(prefix); pw.print("    Amount discharged while screen doze: ");
+            pw.println(getDischargeAmountScreenDoze());
             pw.println(" ");
         } else {
             pw.print(prefix); pw.println("  Device battery use since last full charge");
             pw.print(prefix); pw.print("    Amount discharged (lower bound): ");
-                    pw.println(getLowDischargeAmountSinceCharge());
+            pw.println(getLowDischargeAmountSinceCharge());
             pw.print(prefix); pw.print("    Amount discharged (upper bound): ");
-                    pw.println(getHighDischargeAmountSinceCharge());
+            pw.println(getHighDischargeAmountSinceCharge());
             pw.print(prefix); pw.print("    Amount discharged while screen on: ");
-                    pw.println(getDischargeAmountScreenOnSinceCharge());
+            pw.println(getDischargeAmountScreenOnSinceCharge());
             pw.print(prefix); pw.print("    Amount discharged while screen off: ");
-                    pw.println(getDischargeAmountScreenOffSinceCharge());
+            pw.println(getDischargeAmountScreenOffSinceCharge());
+            pw.print(prefix); pw.print("    Amount discharged while screen doze: ");
+            pw.println(getDischargeAmountScreenDozeSinceCharge());
             pw.println();
         }
 
@@ -5426,8 +5549,9 @@
             }
         }
     }
-    
+
     public void prepareForDumpLocked() {
+        // We don't need to require subclasses implement this.
     }
 
     public static class HistoryPrinter {
@@ -6248,7 +6372,8 @@
             pw.println();
         }
     }
-    
+
+    // This is called from BatteryStatsService.
     @SuppressWarnings("unused")
     public void dumpCheckinLocked(Context context, PrintWriter pw,
             List<ApplicationInfo> apps, int flags, long histStart) {
@@ -6260,10 +6385,7 @@
 
         long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
 
-        final boolean filtering = (flags &
-                (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
-
-        if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) {
+        if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
             if (startIteratingHistoryLocked()) {
                 try {
                     for (int i=0; i<getHistoryStringPoolSize(); i++) {
@@ -6287,7 +6409,7 @@
             }
         }
 
-        if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
+        if ((flags & DUMP_HISTORY_ONLY) != 0) {
             return;
         }
 
@@ -6320,7 +6442,7 @@
                 }
             }
         }
-        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
+        if ((flags & DUMP_DAILY_ONLY) == 0) {
             dumpDurationSteps(pw, "", DISCHARGE_STEP_DATA, getDischargeLevelStepTracker(), true);
             String[] lineArgs = new String[1];
             long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime() * 1000);
@@ -6340,4 +6462,33 @@
                     (flags&DUMP_DEVICE_WIFI_ONLY) != 0);
         }
     }
+
+    /** Dump 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);
+        final long bToken = proto.start(BatteryStatsServiceDumpProto.BATTERYSTATS);
+        prepareForDumpLocked();
+
+        proto.write(BatteryStatsProto.REPORT_VERSION, CHECKIN_VERSION);
+        proto.write(BatteryStatsProto.PARCEL_VERSION, getParcelVersion());
+        proto.write(BatteryStatsProto.START_PLATFORM_VERSION, getStartPlatformVersion());
+        proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion());
+
+        long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
+        if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
+            if (startIteratingHistoryLocked()) {
+                // TODO: implement dumpProtoHistoryLocked(proto);
+            }
+        }
+
+        if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
+            // TODO: implement dumpProtoAppsLocked(proto, apps);
+            // TODO: implement dumpProtoSystemLocked(proto);
+        }
+
+        proto.end(bToken);
+        proto.flush();
+    }
 }
diff --git a/android/os/Binder.java b/android/os/Binder.java
index 0df6361..e9e695b 100644
--- a/android/os/Binder.java
+++ b/android/os/Binder.java
@@ -23,7 +23,6 @@
 import android.util.Slog;
 
 import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
 
@@ -202,7 +201,7 @@
      * then its own pid is returned.
      */
     public static final native int getCallingPid();
-    
+
     /**
      * Return the Linux uid assigned to the process that sent you the
      * current transaction that is being processed.  This uid can be used with
@@ -335,7 +334,7 @@
      * it needs to.
      */
     public static final native void flushPendingCommands();
-    
+
     /**
      * Add the calling thread to the IPC thread pool.  This function does
      * not return until the current process is exiting.
@@ -372,7 +371,7 @@
             }
         }
     }
-    
+
     /**
      * Convenience method for associating a specific interface with the Binder.
      * After calling, queryLocalInterface() will be implemented for you
@@ -383,7 +382,7 @@
         mOwner = owner;
         mDescriptor = descriptor;
     }
-    
+
     /**
      * Default implementation returns an empty interface name.
      */
@@ -408,7 +407,7 @@
     public boolean isBinderAlive() {
         return true;
     }
-    
+
     /**
      * Use information supplied to attachInterface() to return the
      * associated IInterface if it matches the requested
@@ -630,7 +629,7 @@
         }
         return r;
     }
-    
+
     /**
      * Local implementation is a no-op.
      */
@@ -643,7 +642,7 @@
     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
         return true;
     }
-    
+
     protected void finalize() throws Throwable {
         try {
             destroyBinder();
@@ -730,7 +729,15 @@
     }
 }
 
+/**
+ * Java proxy for a native IBinder object.
+ * Allocated and constructed by the native javaObjectforIBinder function. Never allocated
+ * directly from Java code.
+ */
 final class BinderProxy implements IBinder {
+    // See android_util_Binder.cpp for the native half of this.
+    // TODO: Consider using NativeAllocationRegistry instead of finalization.
+
     // Assume the process-wide default value when created
     volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
 
@@ -789,7 +796,7 @@
             reply.recycle();
         }
     }
-    
+
     public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -826,7 +833,7 @@
     BinderProxy() {
         mSelf = new WeakReference(this);
     }
-    
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -835,9 +842,9 @@
             super.finalize();
         }
     }
-    
+
     private native final void destroy();
-    
+
     private static final void sendDeathNotice(DeathRecipient recipient) {
         if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
         try {
@@ -848,8 +855,20 @@
                     exc);
         }
     }
-    
+
+    // This WeakReference to "this" is used only by native code to "attach" to the
+    // native IBinder object.
+    // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a
+    // non-null value after the BinderProxy is enqueued for finalization.
+    // Used only once immediately after construction.
+    // TODO: Consider making the extra native-to-java call to compute this on the fly.
     final private WeakReference mSelf;
+
+    // Native pointer to the wrapped native IBinder object. Counted as strong reference.
     private long mObject;
+
+    // Native pointer to native DeathRecipientList. Counted as strong reference.
+    // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients
+    // hold a weak reference that can be temporarily promoted.
     private long mOrgue;
 }
diff --git a/android/os/IServiceManager.java b/android/os/IServiceManager.java
index 7b11c28..87c65ec 100644
--- a/android/os/IServiceManager.java
+++ b/android/os/IServiceManager.java
@@ -18,12 +18,12 @@
 
 /**
  * Basic interface for finding and publishing system services.
- * 
+ *
  * An implementation of this interface is usually published as the
  * global context object, which can be retrieved via
  * BinderNative.getContextObject().  An easy way to retrieve this
  * is with the static method BnServiceManager.getDefault().
- * 
+ *
  * @hide
  */
 public interface IServiceManager extends IInterface
@@ -33,33 +33,33 @@
      * service manager.  Blocks for a few seconds waiting for it to be
      * published if it does not already exist.
      */
-    public IBinder getService(String name) throws RemoteException;
-    
+    IBinder getService(String name) throws RemoteException;
+
     /**
      * Retrieve an existing service called @a name from the
      * service manager.  Non-blocking.
      */
-    public IBinder checkService(String name) throws RemoteException;
+    IBinder checkService(String name) throws RemoteException;
 
     /**
      * Place a new @a service called @a name into the service
      * manager.
      */
-    public void addService(String name, IBinder service, boolean allowIsolated)
+    void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
                 throws RemoteException;
 
     /**
      * Return a list of all currently running services.
      */
-    public String[] listServices() throws RemoteException;
+    String[] listServices(int dumpPriority) throws RemoteException;
 
     /**
      * Assign a permission controller to the service manager.  After set, this
      * interface is checked before any services are added.
      */
-    public void setPermissionController(IPermissionController controller)
+    void setPermissionController(IPermissionController controller)
             throws RemoteException;
-    
+
     static final String descriptor = "android.os.IServiceManager";
 
     int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
@@ -68,4 +68,13 @@
     int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
     int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
     int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
+
+    /*
+     * Must update values in IServiceManager.h
+     */
+    int DUMP_PRIORITY_CRITICAL = 1 << 0;
+    int DUMP_PRIORITY_HIGH = 1 << 1;
+    int DUMP_PRIORITY_NORMAL = 1 << 2;
+    int DUMP_PRIORITY_ALL = DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_HIGH
+            | DUMP_PRIORITY_NORMAL;
 }
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index 031ca91..857e8a6 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -1340,6 +1340,13 @@
      * @see Parcelable
      */
     public final <T extends Parcelable> void writeTypedList(List<T> val) {
+        writeTypedList(val, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public <T extends Parcelable> void writeTypedList(List<T> val, int parcelableFlags) {
         if (val == null) {
             writeInt(-1);
             return;
@@ -1348,13 +1355,7 @@
         int i=0;
         writeInt(N);
         while (i < N) {
-            T item = val.get(i);
-            if (item != null) {
-                writeInt(1);
-                item.writeToParcel(this, 0);
-            } else {
-                writeInt(0);
-            }
+            writeTypedObject(val.get(i), parcelableFlags);
             i++;
         }
     }
@@ -1456,13 +1457,7 @@
             int N = val.length;
             writeInt(N);
             for (int i = 0; i < N; i++) {
-                T item = val[i];
-                if (item != null) {
-                    writeInt(1);
-                    item.writeToParcel(this, parcelableFlags);
-                } else {
-                    writeInt(0);
-                }
+                writeTypedObject(val[i], parcelableFlags);
             }
         } else {
             writeInt(-1);
@@ -1470,146 +1465,6 @@
     }
 
     /**
-     * Write a uniform (all items are null or the same class) array list of
-     * parcelables.
-     *
-     * @param list The list to write.
-     *
-     * @hide
-     */
-    public final <T extends Parcelable> void writeTypedArrayList(@Nullable ArrayList<T> list,
-            int parcelableFlags) {
-        if (list != null) {
-            int N = list.size();
-            writeInt(N);
-            boolean wroteCreator = false;
-            for (int i = 0; i < N; i++) {
-                T item = list.get(i);
-                if (item != null) {
-                    writeInt(1);
-                    if (!wroteCreator) {
-                        writeParcelableCreator(item);
-                        wroteCreator = true;
-                    }
-                    item.writeToParcel(this, parcelableFlags);
-                } else {
-                    writeInt(0);
-                }
-            }
-        } else {
-            writeInt(-1);
-        }
-    }
-
-    /**
-     * Reads a uniform (all items are null or the same class) array list of
-     * parcelables.
-     *
-     * @return The list or null.
-     *
-     * @hide
-     */
-    public final @Nullable <T> ArrayList<T> readTypedArrayList(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N <= 0) {
-            return null;
-        }
-        Parcelable.Creator<?> creator = null;
-        ArrayList<T> result = new ArrayList<T>(N);
-        for (int i = 0; i < N; i++) {
-            if (readInt() != 0) {
-                if (creator == null) {
-                    creator = readParcelableCreator(loader);
-                    if (creator == null) {
-                        return null;
-                    }
-                }
-                final T parcelable;
-                if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
-                    Parcelable.ClassLoaderCreator<?> classLoaderCreator =
-                            (Parcelable.ClassLoaderCreator<?>) creator;
-                    parcelable = (T) classLoaderCreator.createFromParcel(this, loader);
-                } else {
-                    parcelable = (T) creator.createFromParcel(this);
-                }
-                result.add(parcelable);
-            } else {
-                result.add(null);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Write a uniform (all items are null or the same class) array set of
-     * parcelables.
-     *
-     * @param set The set to write.
-     *
-     * @hide
-     */
-    public final <T extends Parcelable> void writeTypedArraySet(@Nullable ArraySet<T> set,
-            int parcelableFlags) {
-        if (set != null) {
-            int N = set.size();
-            writeInt(N);
-            boolean wroteCreator = false;
-            for (int i = 0; i < N; i++) {
-                T item = set.valueAt(i);
-                if (item != null) {
-                    writeInt(1);
-                    if (!wroteCreator) {
-                        writeParcelableCreator(item);
-                        wroteCreator = true;
-                    }
-                    item.writeToParcel(this, parcelableFlags);
-                } else {
-                    writeInt(0);
-                }
-            }
-        } else {
-            writeInt(-1);
-        }
-    }
-
-    /**
-     * Reads a uniform (all items are null or the same class) array set of
-     * parcelables.
-     *
-     * @return The set or null.
-     *
-     * @hide
-     */
-    public final @Nullable <T> ArraySet<T> readTypedArraySet(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N <= 0) {
-            return null;
-        }
-        Parcelable.Creator<?> creator = null;
-        ArraySet<T> result = new ArraySet<T>(N);
-        for (int i = 0; i < N; i++) {
-            T parcelable = null;
-            if (readInt() != 0) {
-                if (creator == null) {
-                    creator = readParcelableCreator(loader);
-                    if (creator == null) {
-                        return null;
-                    }
-                }
-                if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
-                    Parcelable.ClassLoaderCreator<?> classLoaderCreator =
-                            (Parcelable.ClassLoaderCreator<?>) creator;
-                    parcelable = (T) classLoaderCreator.createFromParcel(this, loader);
-                } else {
-                    parcelable = (T) creator.createFromParcel(this);
-                }
-            }
-            result.append(parcelable);
-        }
-        return result;
-    }
-
-    /**
      * Flatten the Parcelable object into the parcel.
      *
      * @param val The Parcelable object to be written.
@@ -2458,11 +2313,7 @@
         }
         ArrayList<T> l = new ArrayList<T>(N);
         while (N > 0) {
-            if (readInt() != 0) {
-                l.add(c.createFromParcel(this));
-            } else {
-                l.add(null);
-            }
+            l.add(readTypedObject(c));
             N--;
         }
         return l;
@@ -2485,18 +2336,10 @@
         int N = readInt();
         int i = 0;
         for (; i < M && i < N; i++) {
-            if (readInt() != 0) {
-                list.set(i, c.createFromParcel(this));
-            } else {
-                list.set(i, null);
-            }
+            list.set(i, readTypedObject(c));
         }
         for (; i<N; i++) {
-            if (readInt() != 0) {
-                list.add(c.createFromParcel(this));
-            } else {
-                list.add(null);
-            }
+            list.add(readTypedObject(c));
         }
         for (; i<M; i++) {
             list.remove(N);
@@ -2641,9 +2484,7 @@
         }
         T[] l = c.newArray(N);
         for (int i=0; i<N; i++) {
-            if (readInt() != 0) {
-                l[i] = c.createFromParcel(this);
-            }
+            l[i] = readTypedObject(c);
         }
         return l;
     }
@@ -2652,11 +2493,7 @@
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
-                if (readInt() != 0) {
-                    val[i] = c.createFromParcel(this);
-                } else {
-                    val[i] = null;
-                }
+                val[i] = readTypedObject(c);
             }
         } else {
             throw new RuntimeException("bad array lengths");
diff --git a/android/os/ParcelableException.java b/android/os/ParcelableException.java
index d84d629..7f71905 100644
--- a/android/os/ParcelableException.java
+++ b/android/os/ParcelableException.java
@@ -52,10 +52,12 @@
         final String msg = in.readString();
         try {
             final Class<?> clazz = Class.forName(name, true, Parcelable.class.getClassLoader());
-            return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+            if (Throwable.class.isAssignableFrom(clazz)) {
+                return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+            }
         } catch (ReflectiveOperationException e) {
-            throw new RuntimeException(name + ": " + msg);
         }
+        return new RuntimeException(name + ": " + msg);
     }
 
     /** {@hide} */
diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java
index 960c9f5..7f4dee6 100644
--- a/android/os/PowerManager.java
+++ b/android/os/PowerManager.java
@@ -443,6 +443,20 @@
     public static final String SHUTDOWN_USER_REQUESTED = "userrequested";
 
     /**
+     * The value to pass as the 'reason' argument to android_reboot() when battery temperature
+     * is too high.
+     * @hide
+     */
+    public static final String SHUTDOWN_BATTERY_THERMAL_STATE = "thermal,battery";
+
+    /**
+     * The value to pass as the 'reason' argument to android_reboot() when device is running
+     * critically low on battery.
+     * @hide
+     */
+    public static final String SHUTDOWN_LOW_BATTERY = "battery";
+
+    /**
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -451,7 +465,9 @@
             SHUTDOWN_REASON_SHUTDOWN,
             SHUTDOWN_REASON_REBOOT,
             SHUTDOWN_REASON_USER_REQUESTED,
-            SHUTDOWN_REASON_THERMAL_SHUTDOWN
+            SHUTDOWN_REASON_THERMAL_SHUTDOWN,
+            SHUTDOWN_REASON_LOW_BATTERY,
+            SHUTDOWN_REASON_BATTERY_THERMAL
     })
     public @interface ShutdownReason {}
 
@@ -485,6 +501,18 @@
      */
     public static final int SHUTDOWN_REASON_THERMAL_SHUTDOWN = 4;
 
+    /**
+     * constant for shutdown reason being low battery.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_LOW_BATTERY = 5;
+
+    /**
+     * constant for shutdown reason being critical battery thermal state.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_BATTERY_THERMAL = 6;
+
     final Context mContext;
     final IPowerManager mService;
     final Handler mHandler;
@@ -1384,7 +1412,11 @@
          */
         public void release(int flags) {
             synchronized (mToken) {
-                mInternalCount--;
+                if (mInternalCount > 0) {
+                    // internal count must only be decreased if it is > 0 or state of
+                    // the WakeLock object is broken.
+                    mInternalCount--;
+                }
                 if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
                     mExternalCount--;
                 }
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index 34c7845..f41848f 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -16,9 +16,29 @@
 
 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.
@@ -27,14 +47,32 @@
      * @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;
     }
 
     /**
-     * Is not supposed to return null, but that is fine for layoutlib.
+     * Returns a reference to a service with the given name, or throws
+     * {@link NullPointerException} if none is found.
+     *
+     * @hide
      */
     public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
-        throw new ServiceNotFoundException(name);
+        final IBinder binder = getService(name);
+        if (binder != null) {
+            return binder;
+        } else {
+            throw new ServiceNotFoundException(name);
+        }
     }
 
     /**
@@ -45,7 +83,39 @@
      * @param service the service object
      */
     public static void addService(String name, IBinder service) {
-        // pass
+        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);
+        }
     }
 
     /**
@@ -53,7 +123,17 @@
      * service manager.  Non-blocking.
      */
     public static IBinder checkService(String name) {
-        return null;
+        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;
+        }
     }
 
     /**
@@ -62,9 +142,12 @@
      * case of an exception
      */
     public static String[] listServices() {
-        // actual implementation returns null sometimes, so it's ok
-        // to return null instead of an empty list.
-        return null;
+        try {
+            return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL);
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in listServices", e);
+            return null;
+        }
     }
 
     /**
@@ -76,7 +159,10 @@
      * @hide
      */
     public static void initServiceCache(Map<String, IBinder> cache) {
-        // pass
+        if (sCache.size() != 0) {
+            throw new IllegalStateException("setServiceCache may only be called once");
+        }
+        sCache.putAll(cache);
     }
 
     /**
@@ -87,7 +173,6 @@
      * @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/ServiceManagerNative.java b/android/os/ServiceManagerNative.java
index be24426..589b8c4 100644
--- a/android/os/ServiceManagerNative.java
+++ b/android/os/ServiceManagerNative.java
@@ -40,63 +40,65 @@
         if (in != null) {
             return in;
         }
-        
+
         return new ServiceManagerProxy(obj);
     }
-    
+
     public ServiceManagerNative()
     {
         attachInterface(this, descriptor);
     }
-    
+
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
     {
         try {
             switch (code) {
-            case IServiceManager.GET_SERVICE_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String name = data.readString();
-                IBinder service = getService(name);
-                reply.writeStrongBinder(service);
-                return true;
-            }
-    
-            case IServiceManager.CHECK_SERVICE_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String name = data.readString();
-                IBinder service = checkService(name);
-                reply.writeStrongBinder(service);
-                return true;
-            }
-    
-            case IServiceManager.ADD_SERVICE_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String name = data.readString();
-                IBinder service = data.readStrongBinder();
-                boolean allowIsolated = data.readInt() != 0;
-                addService(name, service, allowIsolated);
-                return true;
-            }
-    
-            case IServiceManager.LIST_SERVICES_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String[] list = listServices();
-                reply.writeStringArray(list);
-                return true;
-            }
-            
-            case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                IPermissionController controller
-                        = IPermissionController.Stub.asInterface(
-                                data.readStrongBinder());
-                setPermissionController(controller);
-                return true;
-            }
+                case IServiceManager.GET_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = getService(name);
+                    reply.writeStrongBinder(service);
+                    return true;
+                }
+
+                case IServiceManager.CHECK_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = checkService(name);
+                    reply.writeStrongBinder(service);
+                    return true;
+                }
+
+                case IServiceManager.ADD_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = data.readStrongBinder();
+                    boolean allowIsolated = data.readInt() != 0;
+                    int dumpPriority = data.readInt();
+                    addService(name, service, allowIsolated, dumpPriority);
+                    return true;
+                }
+
+                case IServiceManager.LIST_SERVICES_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    int dumpPriority = data.readInt();
+                    String[] list = listServices(dumpPriority);
+                    reply.writeStringArray(list);
+                    return true;
+                }
+
+                case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    IPermissionController controller =
+                            IPermissionController.Stub.asInterface(
+                                    data.readStrongBinder());
+                    setPermissionController(controller);
+                    return true;
+                }
             }
         } catch (RemoteException e) {
         }
-        
+
         return false;
     }
 
@@ -110,11 +112,11 @@
     public ServiceManagerProxy(IBinder remote) {
         mRemote = remote;
     }
-    
+
     public IBinder asBinder() {
         return mRemote;
     }
-    
+
     public IBinder getService(String name) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -139,7 +141,7 @@
         return binder;
     }
 
-    public void addService(String name, IBinder service, boolean allowIsolated)
+    public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -147,12 +149,13 @@
         data.writeString(name);
         data.writeStrongBinder(service);
         data.writeInt(allowIsolated ? 1 : 0);
+        data.writeInt(dumpPriority);
         mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
         reply.recycle();
         data.recycle();
     }
-    
-    public String[] listServices() throws RemoteException {
+
+    public String[] listServices(int dumpPriority) throws RemoteException {
         ArrayList<String> services = new ArrayList<String>();
         int n = 0;
         while (true) {
@@ -160,6 +163,7 @@
             Parcel reply = Parcel.obtain();
             data.writeInterfaceToken(IServiceManager.descriptor);
             data.writeInt(n);
+            data.writeInt(dumpPriority);
             n++;
             try {
                 boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0);
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index f02631c..826ec1e 100644
--- a/android/os/StrictMode.java
+++ b/android/os/StrictMode.java
@@ -16,6 +16,7 @@
 package android.os;
 
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
@@ -153,12 +154,15 @@
     // Byte 1: Thread-policy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
 
     /**
@@ -166,6 +170,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
 
     /**
@@ -173,9 +178,11 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
 
     private static final int ALL_THREAD_DETECT_BITS =
@@ -193,6 +200,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
 
     /**
@@ -200,6 +208,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
 
     /**
@@ -207,25 +216,32 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
 
     private static final int ALL_VM_DETECT_BITS =
             DETECT_VM_CURSOR_LEAKS
@@ -322,16 +338,36 @@
 
     /** {@hide} */
     @TestApi
-    public interface ViolationListener {
-        public void onViolation(String message);
+    public interface ViolationLogger {
+
+        /** Called when penaltyLog is enabled and a violation needs logging. */
+        void log(ViolationInfo info);
     }
 
-    private static volatile ViolationListener sListener;
+    private static final ViolationLogger LOGCAT_LOGGER =
+            info -> {
+                String msg;
+                if (info.durationMillis != -1) {
+                    msg = "StrictMode policy violation; ~duration=" + info.durationMillis + " ms:";
+                } else {
+                    msg = "StrictMode policy violation:";
+                }
+                if (info.crashInfo != null) {
+                    Log.d(TAG, msg + " " + info.crashInfo.stackTrace);
+                } else {
+                    Log.d(TAG, msg + " missing stack trace!");
+                }
+            };
+
+    private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
 
     /** {@hide} */
     @TestApi
-    public static void setViolationListener(ViolationListener listener) {
-        sListener = listener;
+    public static void setViolationLogger(ViolationLogger listener) {
+        if (listener == null) {
+            listener = LOGCAT_LOGGER;
+        }
+        sLogger = listener;
     }
 
     /**
@@ -1512,28 +1548,16 @@
                     lastViolationTime = vtime;
                 }
             } else {
-                mLastViolationTime = new ArrayMap<Integer, Long>(1);
+                mLastViolationTime = new ArrayMap<>(1);
             }
             long now = SystemClock.uptimeMillis();
             mLastViolationTime.put(crashFingerprint, now);
             long timeSinceLastViolationMillis =
                     lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
 
-            if ((info.policy & PENALTY_LOG) != 0 && sListener != null) {
-                sListener.onViolation(info.crashInfo.stackTrace);
-            }
             if ((info.policy & PENALTY_LOG) != 0
                     && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
-                if (info.durationMillis != -1) {
-                    Log.d(
-                            TAG,
-                            "StrictMode policy violation; ~duration="
-                                    + info.durationMillis
-                                    + " ms: "
-                                    + info.crashInfo.stackTrace);
-                } else {
-                    Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace);
-                }
+                sLogger.log(info);
             }
 
             // The violationMaskSubset, passed to ActivityManager, is a
@@ -1849,6 +1873,10 @@
     }
 
     /** @hide */
+    public static final String CLEARTEXT_DETECTED_MSG =
+            "Detected cleartext network traffic from UID ";
+
+    /** @hide */
     public static void onCleartextNetworkDetected(byte[] firstPacket) {
         byte[] rawAddr = null;
         if (firstPacket != null) {
@@ -1864,14 +1892,10 @@
         }
 
         final int uid = android.os.Process.myUid();
-        String msg = "Detected cleartext network traffic from UID " + uid;
+        String msg = CLEARTEXT_DETECTED_MSG + uid;
         if (rawAddr != null) {
             try {
-                msg =
-                        "Detected cleartext network traffic from UID "
-                                + uid
-                                + " to "
-                                + InetAddress.getByAddress(rawAddr);
+                msg += " to " + InetAddress.getByAddress(rawAddr);
             } catch (UnknownHostException ignored) {
             }
         }
@@ -1882,12 +1906,13 @@
     }
 
     /** @hide */
+    public static final String UNTAGGED_SOCKET_VIOLATION_MESSAGE =
+            "Untagged socket detected; use"
+                    + " TrafficStats.setThreadSocketTag() to track all network usage";
+
+    /** @hide */
     public static void onUntaggedSocket() {
-        onVmPolicyViolation(
-                null,
-                new Throwable(
-                        "Untagged socket detected; use"
-                                + " TrafficStats.setThreadSocketTag() to track all network usage"));
+        onVmPolicyViolation(null, new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
     }
 
     // Map from VM violation fingerprint to uptime millis.
@@ -1925,11 +1950,11 @@
             }
         }
 
-        if (penaltyLog && sListener != null) {
-            sListener.onViolation(originStack.toString());
+        if (penaltyLog && sLogger != null) {
+            sLogger.log(info);
         }
         if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
-            Log.e(TAG, message, originStack);
+            sLogger.log(info);
         }
 
         int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);
@@ -2339,11 +2364,12 @@
      *
      * @hide
      */
-    public static class ViolationInfo implements Parcelable {
+    @TestApi
+    public static final class ViolationInfo implements Parcelable {
         public final String message;
 
         /** Stack and other stuff info. */
-        public final ApplicationErrorReport.CrashInfo crashInfo;
+        @Nullable public final ApplicationErrorReport.CrashInfo crashInfo;
 
         /** The strict mode policy mask at the time of violation. */
         public final int policy;
diff --git a/android/os/UserManagerInternal.java b/android/os/UserManagerInternal.java
index 17f00c2..9369eeb 100644
--- a/android/os/UserManagerInternal.java
+++ b/android/os/UserManagerInternal.java
@@ -154,11 +154,21 @@
     public abstract boolean isUserUnlocked(int userId);
 
     /**
-     * Return whether the given user is running
+     * Returns whether the given user is running
      */
     public abstract boolean isUserRunning(int userId);
 
     /**
+     * Returns whether the given user is initialized
+     */
+    public abstract boolean isUserInitialized(int userId);
+
+    /**
+     * Returns whether the given user exists
+     */
+    public abstract boolean exists(int userId);
+
+    /**
      * Set user's running state
      */
     public abstract void setUserState(int userId, int userState);
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 40ced6c..a062db4 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -6966,8 +6966,9 @@
         public static final String NIGHT_DISPLAY_CUSTOM_END_TIME = "night_display_custom_end_time";
 
         /**
-         * Time in milliseconds (since epoch) when Night display was last activated. Use to decide
-         * whether to apply the current activated state after a reboot or user change.
+         * A String representing the LocalDateTime when Night display was last activated. Use to
+         * decide whether to apply the current activated state after a reboot or user change. In
+         * legacy cases, this is represented by the time in milliseconds (since epoch).
          * @hide
          */
         public static final String NIGHT_DISPLAY_LAST_ACTIVATED_TIME =
@@ -9368,16 +9369,6 @@
         public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
 
         /**
-         * Device Idle (Doze) specific settings for watches. See {@code #DEVICE_IDLE_CONSTANTS}
-         *
-         * <p>
-         * Type: string
-         * @hide
-         * @see com.android.server.DeviceIdleController.Constants
-         */
-        public static final String DEVICE_IDLE_CONSTANTS_WATCH = "device_idle_constants_watch";
-
-        /**
          * Battery Saver specific settings
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -9403,9 +9394,12 @@
 
         /**
          * Battery anomaly detection specific settings
-         * This is encoded as a key=value list, separated by commas. Ex:
+         * This is encoded as a key=value list, separated by commas.
+         * wakeup_blacklisted_tags is a string, encoded as a set of tags, encoded via
+         * {@link Uri#encode(String)}, separated by colons. Ex:
          *
-         * "anomaly_detection_enabled=true,wakelock_threshold=2000"
+         * "anomaly_detection_enabled=true,wakelock_threshold=2000,wakeup_alarm_enabled=true,"
+         * "wakeup_alarm_threshold=10,wakeup_blacklisted_tags=tag1:tag2:with%2Ccomma:with%3Acolon"
          *
          * The following keys are supported:
          *
@@ -9413,6 +9407,11 @@
          * anomaly_detection_enabled       (boolean)
          * wakelock_enabled                (boolean)
          * wakelock_threshold              (long)
+         * wakeup_alarm_enabled            (boolean)
+         * wakeup_alarm_threshold          (long)
+         * wakeup_blacklisted_tags         (string)
+         * bluetooth_scan_enabled          (boolean)
+         * bluetooth_scan_threshold        (long)
          * </pre>
          * @hide
          */
@@ -10199,7 +10198,7 @@
                 "allow_user_switching_when_system_user_locked";
 
         /**
-         * Boot count since the device starts running APK level 24.
+         * Boot count since the device starts running API level 24.
          * <p>
          * Type: int
          */
@@ -10893,6 +10892,26 @@
          */
         public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE =
                 "enable_deletion_helper_no_threshold_toggle";
+
+        /**
+         * The list of snooze options for notifications
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "default=60,options_array=15:30:60:120"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * default               (int)
+         * options_array         (string)
+         * </pre>
+         *
+         * All delays in integer minutes. Array order is respected.
+         * Options will be used in order up to the maximum allowed by the UI.
+         * @hide
+         */
+        public static final String NOTIFICATION_SNOOZE_OPTIONS =
+                "notification_snooze_options";
     }
 
     /**
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
index 3e08dcf..2e59f6c 100644
--- a/android/service/autofill/AutofillService.java
+++ b/android/service/autofill/AutofillService.java
@@ -187,7 +187,7 @@
  * protect a dataset that contains sensitive information by requiring dataset authentication
  * (see {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}), and to include
  * info about the "primary" field of the partition in the custom presentation for "secondary"
- * fields &mdash; that would prevent a malicious app from getting the "primary" fields without the
+ * fields&mdash;that would prevent a malicious app from getting the "primary" fields without the
  * user realizing they're being released (for example, a malicious app could have fields for a
  * credit card number, verification code, and expiration date crafted in a way that just the latter
  * is visible; by explicitly indicating the expiration date is related to a given credit card
@@ -305,7 +305,7 @@
  *   <li>Use the {@link android.app.assist.AssistStructure.ViewNode#getWebDomain()} to get the
  *       source of the document.
  *   <li>Get the canonical domain using the
- *       <a href="https://publicsuffix.org/>Public Suffix List</a> (see example below).
+ *       <a href="https://publicsuffix.org/">Public Suffix List</a> (see example below).
  *   <li>Use <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a>
  *       to obtain the package name and certificate fingerprint of the package corresponding to
  *       the canonical domain.
@@ -503,13 +503,19 @@
             @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
 
     /**
-     * Called when user requests service to save the fields of a screen.
+     * Called when the user requests the service to save the contents of a screen.
      *
      * <p>Service must call one of the {@link SaveCallback} methods (like
      * {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)})
-     * to notify the result of the request.
+     * to notify the Android System of the result of the request.
      *
-     * <p><b>Note:</b> To retrieve the actual value of the field, the service should call
+     * <p>If the service could not handle the request right away&mdash;for example, because it must
+     * launch an activity asking the user to authenticate first or because the network is
+     * down&mdash;the service could keep the {@link SaveRequest request} and reuse it later,
+     * but the service must call {@link SaveCallback#onSuccess()} right away.
+     *
+     * <p><b>Note:</b> To retrieve the actual value of fields input by the user, the service
+     * should call
      * {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}; if it calls
      * {@link android.app.assist.AssistStructure.ViewNode#getText()} or other methods, there is no
      * guarantee such method will return the most recent value of the field.
diff --git a/android/service/autofill/AutofillServiceInfo.java b/android/service/autofill/AutofillServiceInfo.java
index f147400..5c7388f 100644
--- a/android/service/autofill/AutofillServiceInfo.java
+++ b/android/service/autofill/AutofillServiceInfo.java
@@ -146,4 +146,9 @@
     public String getSettingsActivity() {
         return mSettingsActivity;
     }
+
+    @Override
+    public String toString() {
+        return mServiceInfo == null ? "null" : mServiceInfo.toString();
+    }
 }
diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java
index cb341b1..ef9598a 100644
--- a/android/service/autofill/Dataset.java
+++ b/android/service/autofill/Dataset.java
@@ -29,32 +29,77 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.regex.Pattern;
 
 /**
- * A dataset object represents a group of key/value pairs used to autofill parts of a screen.
+ * A dataset object represents a group of fields (key / value pairs) used to autofill parts of a
+ * screen.
  *
- * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of
- * {@link AutofillId} and {@link AutofillValue} respectively); and one or more
- * {@link RemoteViews presentation} for these pairs (a pair could have its own
- * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated
- * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse}
+ * <a name="BasicUsage"></a>
+ * <h3>Basic usage</h3>
+ *
+ * <p>In its simplest form, a dataset contains one or more fields (comprised of
+ * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter
+ * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields
+ * (each field could have its own {@link RemoteViews presentation}, or use the default
+ * {@link RemoteViews presentation} associated with the whole dataset).
+ *
+ * <p>When an autofill service returns datasets in a {@link FillResponse}
  * and the screen input is focused in a view that is present in at least one of these datasets,
- * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of
+ * the Android System displays a UI containing the {@link RemoteViews presentation} of
  * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
- * dataset from the affordance, all views in that dataset are autofilled.
+ * dataset from the UI, all views in that dataset are autofilled.
  *
- * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates
- * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}.
+ * <a name="Authentication"></a>
+ * <h3>Dataset authentication</h3>
  *
- * @see android.service.autofill.AutofillService for more information and examples about the
- * role of datasets in the autofill workflow.
+ * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates
+ * the dataset&mdash;in that case, when a dataset is selected by the user, the Android System
+ * launches an intent set by the service to "unlock" the dataset.
+ *
+ * <p>For example, when a data set contains credit card information (such as number,
+ * expiration date, and verification code), you could provide a dataset presentation saying
+ * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking
+ * the user to enter the credit card code, and if the user enters a valid code, you could then
+ * "unlock" the dataset.
+ *
+ * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example,
+ * if the activity being autofilled is an account creation screen, you could use an authenticated
+ * dataset to automatically generate a random password for the user.
+ *
+ * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset
+ * authentication mechanism.
+ *
+ * <a name="Filtering"></a>
+ * <h3>Filtering</h3>
+ * <p>The autofill UI automatically changes which values are shown based on value of the view
+ * anchoring it, following the rules below:
+ * <ol>
+ *   <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
+ * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
+ *   <li>Datasets that have a filter regex (set through
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
+ * regex matches the view's text value converted to lower case are shown.
+ *   <li>Datasets that do not require authentication, have a field value that is
+ * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
+ * with the lower case value of the view's text are shown.
+ *   <li>All other datasets are hidden.
+ * </ol>
+ *
+ * <a name="MoreInfo"></a>
+ * <h3>More information</h3>
+ * <p>See {@link android.service.autofill.AutofillService} for more information and examples about
+ * the role of datasets in the autofill workflow.
  */
 public final class Dataset implements Parcelable {
 
     private final ArrayList<AutofillId> mFieldIds;
     private final ArrayList<AutofillValue> mFieldValues;
     private final ArrayList<RemoteViews> mFieldPresentations;
+    private final ArrayList<Pattern> mFieldFilters;
     private final RemoteViews mPresentation;
     private final IntentSender mAuthentication;
     @Nullable String mId;
@@ -63,6 +108,7 @@
         mFieldIds = builder.mFieldIds;
         mFieldValues = builder.mFieldValues;
         mFieldPresentations = builder.mFieldPresentations;
+        mFieldFilters = builder.mFieldFilters;
         mPresentation = builder.mPresentation;
         mAuthentication = builder.mAuthentication;
         mId = builder.mId;
@@ -85,6 +131,12 @@
     }
 
     /** @hide */
+    @Nullable
+    public Pattern getFilter(int index) {
+        return mFieldFilters.get(index);
+    }
+
+    /** @hide */
     public @Nullable IntentSender getAuthentication() {
         return mAuthentication;
     }
@@ -103,6 +155,8 @@
                 .append(", fieldValues=").append(mFieldValues)
                 .append(", fieldPresentations=")
                 .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
+                .append(", fieldFilters=")
+                .append(mFieldFilters == null ? 0 : mFieldFilters.size())
                 .append(", hasPresentation=").append(mPresentation != null)
                 .append(", hasAuthentication=").append(mAuthentication != null)
                 .append(']').toString();
@@ -127,6 +181,7 @@
         private ArrayList<AutofillId> mFieldIds;
         private ArrayList<AutofillValue> mFieldValues;
         private ArrayList<RemoteViews> mFieldPresentations;
+        private ArrayList<Pattern> mFieldFilters;
         private RemoteViews mPresentation;
         private IntentSender mAuthentication;
         private boolean mDestroyed;
@@ -182,12 +237,12 @@
          * credit card information without the CVV for the data set in the {@link FillResponse
          * response} then the returned data set should contain the CVV entry.
          *
-         * <p><b>NOTE:</b> Do not make the provided pending intent
+         * <p><b>Note:</b> Do not make the provided pending intent
          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
          * platform needs to fill in the authentication arguments.
          *
          * @param authentication Intent to an activity with your authentication flow.
-         * @return This builder.
+         * @return this builder.
          *
          * @see android.app.PendingIntent
          */
@@ -214,11 +269,10 @@
          *
          * @param id id for this dataset or {@code null} to unset.
          *
-         * @return This builder.
+         * @return this builder.
          */
         public @NonNull Builder setId(@Nullable String id) {
             throwIfDestroyed();
-
             mId = id;
             return this;
         }
@@ -230,17 +284,16 @@
          *         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 an authentication and you have no access to the value.
-         * @return This builder.
+         *        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();
-            if (mPresentation == null) {
-                throw new IllegalStateException("Dataset presentation not set on constructor");
-            }
-            setValueAndPresentation(id, value, null);
+            Preconditions.checkState(mPresentation != null,
+                    "Dataset presentation not set on constructor");
+            setLifeTheUniverseAndEverything(id, value, null, null);
             return this;
         }
 
@@ -250,23 +303,81 @@
          *
          * @param id id returned by {@link
          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
-         * @param value value to be auto filled. Pass {@code null} if you do not have the value
+         * @param value the 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 an authentication and you have no access to the value.
-         *        Filtering matches any user typed string to {@code null} values.
-         * @param presentation The presentation used to visualize this field.
-         * @return This builder.
+         *        the dataset needs authentication and you have no access to the value.
+         * @param presentation the presentation used to visualize this field.
+         * @return this builder.
+         *
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @NonNull RemoteViews presentation) {
             throwIfDestroyed();
             Preconditions.checkNotNull(presentation, "presentation cannot be null");
-            setValueAndPresentation(id, value, presentation);
+            setLifeTheUniverseAndEverything(id, value, presentation, null);
             return this;
         }
 
-        private void setValueAndPresentation(AutofillId id, AutofillValue value,
-                RemoteViews presentation) {
+        /**
+         * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
+         *
+         * <p>This method is typically used when the dataset is not authenticated and the field
+         * value is not {@link AutofillValue#isText() text} but the service still wants to allow
+         * the user to filter it out.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the 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.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI.
+         *
+         * @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,
+                @NonNull Pattern filter) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(filter, "filter cannot be null");
+            Preconditions.checkState(mPresentation != null,
+                    "Dataset presentation not set on constructor");
+            setLifeTheUniverseAndEverything(id, value, null, filter);
+            return this;
+        }
+
+        /**
+         * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+         * visualize it and a <a href="#Filtering">explicit filter</a>.
+         *
+         * <p>Typically used to allow filtering on
+         * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated datasets}. For
+         * example, if the dataset represents a credit card number and the service does not want to
+         * show the "Tap to authenticate" message until the user tapped 4 digits, in which case
+         * the filter would be {@code Pattern.compile("\\d.{4,}")}.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the 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.
+         * @param presentation the presentation used to visualize this field.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @NonNull Pattern filter, @NonNull RemoteViews presentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(filter, "filter cannot be null");
+            Preconditions.checkNotNull(presentation, "presentation cannot be null");
+            setLifeTheUniverseAndEverything(id, value, presentation, filter);
+            return this;
+        }
+
+        private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
+                @Nullable AutofillValue value, @Nullable RemoteViews presentation,
+                @Nullable Pattern filter) {
             Preconditions.checkNotNull(id, "id cannot be null");
             if (mFieldIds != null) {
                 final int existingIdx = mFieldIds.indexOf(id);
@@ -279,10 +390,12 @@
                 mFieldIds = new ArrayList<>();
                 mFieldValues = new ArrayList<>();
                 mFieldPresentations = new ArrayList<>();
+                mFieldFilters = new ArrayList<>();
             }
             mFieldIds.add(id);
             mFieldValues.add(value);
             mFieldPresentations.add(presentation);
+            mFieldFilters.add(filter);
         }
 
         /**
@@ -290,8 +403,9 @@
          *
          * <p>You should not interact with this builder once this method is called.
          *
-         * <p>It is required that you specify at least one field before calling this method. It's
-         * also mandatory to provide a presentation view to visualize the data set in the UI.
+         * @throws IllegalStateException if no field was set (through
+         * {@link #setValue(AutofillId, AutofillValue)} or
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}).
          *
          * @return The built dataset.
          */
@@ -299,7 +413,7 @@
             throwIfDestroyed();
             mDestroyed = true;
             if (mFieldIds == null) {
-                throw new IllegalArgumentException("at least one value must be set");
+                throw new IllegalStateException("at least one value must be set");
             }
             return new Dataset(this);
         }
@@ -323,9 +437,10 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeParcelable(mPresentation, flags);
-        parcel.writeTypedArrayList(mFieldIds, flags);
-        parcel.writeTypedArrayList(mFieldValues, flags);
+        parcel.writeTypedList(mFieldIds, flags);
+        parcel.writeTypedList(mFieldValues, flags);
         parcel.writeParcelableList(mFieldPresentations, flags);
+        parcel.writeSerializable(mFieldFilters);
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeString(mId);
     }
@@ -340,10 +455,14 @@
             final Builder builder = (presentation == null)
                     ? new Builder()
                     : new Builder(presentation);
-            final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
-            final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
+            final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
+            final ArrayList<AutofillValue> values =
+                    parcel.createTypedArrayList(AutofillValue.CREATOR);
             final ArrayList<RemoteViews> presentations = new ArrayList<>();
             parcel.readParcelableList(presentations, null);
+            @SuppressWarnings("unchecked")
+            final ArrayList<Serializable> filters =
+                    (ArrayList<Serializable>) parcel.readSerializable();
             final int idCount = (ids != null) ? ids.size() : 0;
             final int valueCount = (values != null) ? values.size() : 0;
             for (int i = 0; i < idCount; i++) {
@@ -351,7 +470,8 @@
                 final AutofillValue value = (valueCount > i) ? values.get(i) : null;
                 final RemoteViews fieldPresentation = presentations.isEmpty() ? null
                         : presentations.get(i);
-                builder.setValueAndPresentation(id, value, fieldPresentation);
+                final Pattern filter = (Pattern) filters.get(i);
+                builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter);
             }
             builder.setAuthentication(parcel.readParcelable(null));
             builder.setId(parcel.readString());
diff --git a/android/service/autofill/ImageTransformation.java b/android/service/autofill/ImageTransformation.java
index 2151f74..4afda24 100644
--- a/android/service/autofill/ImageTransformation.java
+++ b/android/service/autofill/ImageTransformation.java
@@ -20,11 +20,12 @@
 
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.view.autofill.AutofillId;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
@@ -43,9 +44,9 @@
  *
  * <pre class="prettyprint">
  *   new ImageTransformation.Builder(ccNumberId, 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)
+ *                                   R.drawable.ic_credit_card_logo1, "Brand 1")
+ *     .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2, "Brand 2")
+ *     .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3, "Brand 3")
  *     .build();
  * </pre>
  *
@@ -59,7 +60,7 @@
     private static final String TAG = "ImageTransformation";
 
     private final AutofillId mId;
-    private final ArrayList<Pair<Pattern, Integer>> mOptions;
+    private final ArrayList<Option> mOptions;
 
     private ImageTransformation(Builder builder) {
         mId = builder.mId;
@@ -82,17 +83,21 @@
         }
 
         for (int i = 0; i < size; i++) {
-            final Pair<Pattern, Integer> option = mOptions.get(i);
+            final Option option = mOptions.get(i);
             try {
-                if (option.first.matcher(value).matches()) {
+                if (option.pattern.matcher(value).matches()) {
                     Log.d(TAG, "Found match at " + i + ": " + option);
-                    parentTemplate.setImageViewResource(childViewId, option.second);
+                    parentTemplate.setImageViewResource(childViewId, option.resId);
+                    if (option.contentDescription != null) {
+                        parentTemplate.setContentDescription(childViewId,
+                                option.contentDescription);
+                    }
                     return;
                 }
             } catch (Exception e) {
                 // Do not log full exception to avoid PII leaking
-                Log.w(TAG, "Error matching regex #" + i + "(" + option.first.pattern() + ") on id "
-                        + option.second + ": " + e.getClass());
+                Log.w(TAG, "Error matching regex #" + i + "(" + option.pattern + ") on id "
+                        + option.resId + ": " + e.getClass());
                 throw e;
 
             }
@@ -105,25 +110,44 @@
      */
     public static class Builder {
         private final AutofillId mId;
-        private final ArrayList<Pair<Pattern, Integer>> mOptions = new ArrayList<>();
+        private final ArrayList<Option> mOptions = new ArrayList<>();
         private boolean mDestroyed;
 
         /**
-         * Create a new builder for a autofill id and add a first option.
+         * Creates a new builder for a autofill id and add a first option.
          *
          * @param id id of the screen field that will be used to evaluate whether the image should
          * be used.
          * @param regex regular expression defining what should be matched to use this image.
          * @param resId resource id of the image (in the autofill service's package). The
          * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         *
+         * @deprecated use
+         * {@link #ImageTransformation.Builder(AutofillId, Pattern, int, CharSequence)} instead.
          */
+        @Deprecated
         public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId) {
             mId = Preconditions.checkNotNull(id);
-
             addOption(regex, resId);
         }
 
         /**
+         * Creates a new builder for a autofill id and add a first option.
+         *
+         * @param id id of the screen field that will be used to evaluate whether the image should
+         * be used.
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         * @param contentDescription content description to be applied in the child view.
+         */
+        public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId,
+                @NonNull CharSequence contentDescription) {
+            mId = Preconditions.checkNotNull(id);
+            addOption(regex, resId, contentDescription);
+        }
+
+        /**
          * Adds an option to replace the child view with a different image when the regex matches.
          *
          * @param regex regular expression defining what should be matched to use this image.
@@ -131,17 +155,43 @@
          * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
          *
          * @return this build
+         *
+         * @deprecated use {@link #addOption(Pattern, int, CharSequence)} instead.
          */
+        @Deprecated
         public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId) {
+            addOptionInternal(regex, resId, null);
+            return this;
+        }
+
+        /**
+         * Adds an option to replace the child view with a different image and content description
+         * when the regex matches.
+         *
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         * @param contentDescription content description to be applied in the child view.
+         *
+         * @return this build
+         */
+        public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId,
+                @NonNull CharSequence contentDescription) {
+            addOptionInternal(regex, resId, Preconditions.checkNotNull(contentDescription));
+            return this;
+        }
+
+        private void addOptionInternal(@NonNull Pattern regex, @DrawableRes int resId,
+                @Nullable CharSequence contentDescription) {
             throwIfDestroyed();
 
             Preconditions.checkNotNull(regex);
             Preconditions.checkArgument(resId != 0);
 
-            mOptions.add(new Pair<>(regex, resId));
-            return this;
+            mOptions.add(new Option(regex, resId, contentDescription));
         }
 
+
         /**
          * Creates a new {@link ImageTransformation} instance.
          */
@@ -178,15 +228,18 @@
         parcel.writeParcelable(mId, flags);
 
         final int size = mOptions.size();
-        final Pattern[] regexs = new Pattern[size];
+        final Pattern[] patterns = new Pattern[size];
         final int[] resIds = new int[size];
+        final CharSequence[] contentDescriptions = new String[size];
         for (int i = 0; i < size; i++) {
-            Pair<Pattern, Integer> regex = mOptions.get(i);
-            regexs[i] = regex.first;
-            resIds[i] = regex.second;
+            final Option option = mOptions.get(i);
+            patterns[i] = option.pattern;
+            resIds[i] = option.resId;
+            contentDescriptions[i] = option.contentDescription;
         }
-        parcel.writeSerializable(regexs);
+        parcel.writeSerializable(patterns);
         parcel.writeIntArray(resIds);
+        parcel.writeCharSequenceArray(contentDescriptions);
     }
 
     public static final Parcelable.Creator<ImageTransformation> CREATOR =
@@ -197,15 +250,22 @@
 
             final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
             final int[] resIds = parcel.createIntArray();
+            final CharSequence[] contentDescriptions = parcel.readCharSequenceArray();
 
             // 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 ImageTransformation.Builder builder = new ImageTransformation.Builder(id,
-                    regexs[0], resIds[0]);
+            final CharSequence contentDescription = contentDescriptions[0];
+            final ImageTransformation.Builder builder = (contentDescription != null)
+                    ? new ImageTransformation.Builder(id, regexs[0], resIds[0], contentDescription)
+                    : new ImageTransformation.Builder(id, regexs[0], resIds[0]);
 
             final int size = regexs.length;
             for (int i = 1; i < size; i++) {
-                builder.addOption(regexs[i], resIds[i]);
+                if (contentDescriptions[i] != null) {
+                    builder.addOption(regexs[i], resIds[i], contentDescriptions[i]);
+                } else {
+                    builder.addOption(regexs[i], resIds[i]);
+                }
             }
 
             return builder.build();
@@ -216,4 +276,16 @@
             return new ImageTransformation[size];
         }
     };
+
+    private static final class Option {
+        public final Pattern pattern;
+        public final int resId;
+        public final CharSequence contentDescription;
+
+        Option(Pattern pattern, int resId, CharSequence contentDescription) {
+            this.pattern = pattern;
+            this.resId = resId;
+            this.contentDescription = TextUtils.trimNoCopySpans(contentDescription);
+        }
+    }
 }
diff --git a/android/service/autofill/InternalSanitizer.java b/android/service/autofill/InternalSanitizer.java
new file mode 100644
index 0000000..95d2f66
--- /dev/null
+++ b/android/service/autofill/InternalSanitizer.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 android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+import android.view.autofill.AutofillValue;
+
+/**
+ * Superclass of all sanitizers the system understands. As this is not public all public subclasses
+ * have to implement {@link Sanitizer} again.
+ *
+ * @hide
+ */
+@TestApi
+public abstract class InternalSanitizer implements Sanitizer, Parcelable {
+
+    /**
+     * Sanitizes an {@link AutofillValue}.
+     *
+     * @hide
+     */
+    public abstract AutofillValue sanitize(@NonNull AutofillValue value);
+}
diff --git a/android/service/autofill/Sanitizer.java b/android/service/autofill/Sanitizer.java
new file mode 100644
index 0000000..38757ac
--- /dev/null
+++ b/android/service/autofill/Sanitizer.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Helper class used to sanitize user input before using it in a save request.
+ *
+ * <p>Typically used to avoid displaying the save UI for values that are autofilled but reformatted
+ * by the app&mdash;for example, if the autofill service sends a credit card number
+ * value as "004815162342108" and the app automatically changes it to "0048 1516 2342 108".
+ */
+public interface Sanitizer {
+}
diff --git a/android/service/autofill/SaveCallback.java b/android/service/autofill/SaveCallback.java
index 3a70138..7207f1d 100644
--- a/android/service/autofill/SaveCallback.java
+++ b/android/service/autofill/SaveCallback.java
@@ -34,9 +34,13 @@
 
     /**
      * Notifies the Android System that an
-     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully fulfilled
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
      * by the service.
      *
+     * <p>If the service could not handle the request right away&mdash;for example, because it must
+     * launch an activity asking the user to authenticate first or because the network is
+     * down&mdash;it should still call {@link #onSuccess()}.
+     *
      * @throws RuntimeException if an error occurred while calling the Android System.
      */
     public void onSuccess() {
@@ -51,9 +55,16 @@
 
     /**
      * Notifies the Android System that an
-     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be fulfilled
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be handled
      * by the service.
      *
+     * <p>This method should only be called when the service could not handle the request right away
+     * and could not recover or retry it. If the service could retry or recover, it could keep
+     * the {@link SaveRequest} and call {@link #onSuccess()} instead.
+     *
+     * <p><b>Note:</b> The Android System displays an UI with the supplied error message; if
+     * you prefer to show your own message, call {@link #onSuccess()} instead.
+     *
      * @param message error message to be displayed to the user.
      *
      * @throws RuntimeException if an error occurred while calling the Android System.
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index e0a0730..1b9240c 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -25,6 +25,8 @@
 import android.content.IntentSender;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
@@ -232,6 +234,8 @@
     private final int mFlags;
     private final CustomDescription mCustomDescription;
     private final InternalValidator mValidator;
+    private final InternalSanitizer[] mSanitizerKeys;
+    private final AutofillId[][] mSanitizerValues;
 
     private SaveInfo(Builder builder) {
         mType = builder.mType;
@@ -243,6 +247,18 @@
         mFlags = builder.mFlags;
         mCustomDescription = builder.mCustomDescription;
         mValidator = builder.mValidator;
+        if (builder.mSanitizers == null) {
+            mSanitizerKeys = null;
+            mSanitizerValues = null;
+        } else {
+            final int size = builder.mSanitizers.size();
+            mSanitizerKeys = new InternalSanitizer[size];
+            mSanitizerValues = new AutofillId[size][];
+            for (int i = 0; i < size; i++) {
+                mSanitizerKeys[i] = builder.mSanitizers.keyAt(i);
+                mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
+            }
+        }
     }
 
     /** @hide */
@@ -292,6 +308,18 @@
         return mValidator;
     }
 
+    /** @hide */
+    @Nullable
+    public InternalSanitizer[] getSanitizerKeys() {
+        return mSanitizerKeys;
+    }
+
+    /** @hide */
+    @Nullable
+    public AutofillId[][] getSanitizerValues() {
+        return mSanitizerValues;
+    }
+
     /**
      * A builder for {@link SaveInfo} objects.
      */
@@ -307,6 +335,9 @@
         private int mFlags;
         private CustomDescription mCustomDescription;
         private InternalValidator mValidator;
+        private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
+        // Set used to validate against duplicate ids.
+        private ArraySet<AutofillId> mSanitizerIds;
 
         /**
          * Creates a new builder.
@@ -530,6 +561,61 @@
         }
 
         /**
+         * Adds a sanitizer for one or more field.
+         *
+         * <p>When a sanitizer is set for a field, the {@link AutofillValue} is sent to the
+         * sanitizer before a save request is <a href="#TriggeringSaveRequest">triggered</a>.
+         *
+         * <p>Typically used to avoid displaying the save UI for values that are autofilled but
+         * reformattedby the app. For example, to remove spaces between every 4 digits of a
+         * credit card number:
+         *
+         * <pre class="prettyprint">
+         * builder.addSanitizer(
+         *     new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"),
+         *         "$1$2$3$4"), ccNumberId);
+         * </pre>
+         *
+         * <p>The same sanitizer can be reused to sanitize multiple fields. For example, to trim
+         * both the username and password fields:
+         *
+         * <pre class="prettyprint">
+         * builder.addSanitizer(
+         *     new TextValueSanitizer(Pattern.compile("^\\s*(.*)\\s*$"), "$1"),
+         *         usernameId, passwordId);
+         * </pre>
+         *
+         * @param sanitizer an implementation provided by the Android System.
+         * @param ids id of fields whose value will be sanitized.
+         * @return this builder.
+         *
+         * @throws IllegalArgumentException if a sanitizer for any of the {@code ids} has already
+         * been added or if {@code ids} is empty.
+         */
+        public @NonNull Builder addSanitizer(@NonNull Sanitizer sanitizer,
+                @NonNull AutofillId... ids) {
+            throwIfDestroyed();
+            Preconditions.checkArgument(!ArrayUtils.isEmpty(ids), "ids cannot be empty or null");
+            Preconditions.checkArgument((sanitizer instanceof InternalSanitizer),
+                    "not provided by Android System: " + sanitizer);
+
+            if (mSanitizers == null) {
+                mSanitizers = new ArrayMap<>();
+                mSanitizerIds = new ArraySet<>(ids.length);
+            }
+
+            // Check for duplicates first.
+            for (AutofillId id : ids) {
+                Preconditions.checkArgument(!mSanitizerIds.contains(id), "already added %s", id);
+                mSanitizerIds.add(id);
+            }
+
+            mSanitizers.put((InternalSanitizer) sanitizer, ids);
+
+            return this;
+        }
+
+        /**
          * Builds a new {@link SaveInfo} instance.
          *
          * @throws IllegalStateException if no
@@ -569,6 +655,10 @@
                 .append(", mFlags=").append(mFlags)
                 .append(", mCustomDescription=").append(mCustomDescription)
                 .append(", validation=").append(mValidator)
+                .append(", sanitizerKeys=")
+                    .append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
+                .append(", sanitizerValues=")
+                    .append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
                 .append("]").toString();
     }
 
@@ -591,6 +681,12 @@
         parcel.writeCharSequence(mDescription);
         parcel.writeParcelable(mCustomDescription, flags);
         parcel.writeParcelable(mValidator, flags);
+        parcel.writeParcelableArray(mSanitizerKeys, flags);
+        if (mSanitizerKeys != null) {
+            for (int i = 0; i < mSanitizerValues.length; i++) {
+                parcel.writeParcelableArray(mSanitizerValues[i], flags);
+            }
+        }
         parcel.writeInt(mFlags);
     }
 
@@ -621,6 +717,16 @@
             if (validator != null) {
                 builder.setValidator(validator);
             }
+            final InternalSanitizer[] sanitizers =
+                    parcel.readParcelableArray(null, InternalSanitizer.class);
+            if (sanitizers != null) {
+                final int size = sanitizers.length;
+                for (int i = 0; i < size; i++) {
+                    final AutofillId[] autofillIds =
+                            parcel.readParcelableArray(null, AutofillId.class);
+                    builder.addSanitizer(sanitizers[i], autofillIds);
+                }
+            }
             builder.setFlags(parcel.readInt());
             return builder.build();
         }
diff --git a/android/service/autofill/SaveRequest.java b/android/service/autofill/SaveRequest.java
index 1a6c5b0..65fdb5c 100644
--- a/android/service/autofill/SaveRequest.java
+++ b/android/service/autofill/SaveRequest.java
@@ -48,7 +48,8 @@
     }
 
     private SaveRequest(@NonNull Parcel parcel) {
-        this(parcel.readTypedArrayList(null), parcel.readBundle(), parcel.createStringArrayList());
+        this(parcel.createTypedArrayList(FillContext.CREATOR),
+                parcel.readBundle(), parcel.createStringArrayList());
     }
 
     /**
@@ -84,7 +85,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeTypedArrayList(mFillContexts, flags);
+        parcel.writeTypedList(mFillContexts, flags);
         parcel.writeBundle(mClientState);
         parcel.writeStringList(mDatasetIds);
     }
diff --git a/android/service/autofill/TextValueSanitizer.java b/android/service/autofill/TextValueSanitizer.java
new file mode 100644
index 0000000..12e85b1
--- /dev/null
+++ b/android/service/autofill/TextValueSanitizer.java
@@ -0,0 +1,122 @@
+/*
+ * 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.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Sanitizes a text {@link AutofillValue} using a regular expression (regex) substitution.
+ *
+ * <p>For example, to remove spaces from groups of 4-digits in a credit card:
+ *
+ * <pre class="prettyprint">
+ * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"), "$1$2$3$4")
+ * </pre>
+ */
+public final class TextValueSanitizer extends InternalSanitizer implements
+        Sanitizer, Parcelable {
+    private static final String TAG = "TextValueSanitizer";
+
+    private final Pattern mRegex;
+    private final String mSubst;
+
+    /**
+     * Default constructor.
+     *
+     * @param regex regular expression with groups (delimited by {@code (} and {@code (}) that
+     * are used to substitute parts of the {@link AutofillValue#getTextValue() text value}.
+     * @param subst the string that substitutes the matched regex, using {@code $} for
+     * group substitution ({@code $1} for 1st group match, {@code $2} for 2nd, etc).
+     */
+    public TextValueSanitizer(@NonNull Pattern regex, @NonNull String subst) {
+        mRegex = Preconditions.checkNotNull(regex);
+        mSubst = Preconditions.checkNotNull(subst);
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    public AutofillValue sanitize(@NonNull AutofillValue value) {
+        if (value == null) {
+            Slog.w(TAG, "sanitize() called with null value");
+            return null;
+        }
+        if (!value.isText()) return value;
+
+        final CharSequence text = value.getTextValue();
+
+        try {
+            final Matcher matcher = mRegex.matcher(text);
+            if (!matcher.matches()) return value;
+
+            final CharSequence sanitized = matcher.replaceAll(mSubst);
+            return AutofillValue.forText(sanitized);
+        } catch (Exception e) {
+            Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e);
+            return value;
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "TextValueSanitizer: [regex=" + mRegex + ", subst=" + mSubst + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeSerializable(mRegex);
+        parcel.writeString(mSubst);
+    }
+
+    public static final Parcelable.Creator<TextValueSanitizer> CREATOR =
+            new Parcelable.Creator<TextValueSanitizer>() {
+        @Override
+        public TextValueSanitizer createFromParcel(Parcel parcel) {
+            return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString());
+        }
+
+        @Override
+        public TextValueSanitizer[] newArray(int size) {
+            return new TextValueSanitizer[size];
+        }
+    };
+}
diff --git a/android/service/carrier/CarrierService.java b/android/service/carrier/CarrierService.java
index 813acc2..2707f14 100644
--- a/android/service/carrier/CarrierService.java
+++ b/android/service/carrier/CarrierService.java
@@ -17,10 +17,13 @@
 import android.annotation.CallSuper;
 import android.app.Service;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.util.Log;
 
 import com.android.internal.telephony.ITelephonyRegistry;
 
@@ -48,6 +51,8 @@
  */
 public abstract class CarrierService extends Service {
 
+    private static final String LOG_TAG = "CarrierService";
+
     public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
 
     private static ITelephonyRegistry sRegistry;
@@ -133,11 +138,26 @@
     /**
      * A wrapper around ICarrierService that forwards calls to implementations of
      * {@link CarrierService}.
+     * @hide
      */
-    private class ICarrierServiceWrapper extends ICarrierService.Stub {
+    public class ICarrierServiceWrapper extends ICarrierService.Stub {
+        /** @hide */
+        public static final int RESULT_OK = 0;
+        /** @hide */
+        public static final int RESULT_ERROR = 1;
+        /** @hide */
+        public static final String KEY_CONFIG_BUNDLE = "config_bundle";
+
         @Override
-        public PersistableBundle getCarrierConfig(CarrierIdentifier id) {
-            return CarrierService.this.onLoadConfig(id);
+        public void getCarrierConfig(CarrierIdentifier id, ResultReceiver result) {
+            try {
+                Bundle data = new Bundle();
+                data.putParcelable(KEY_CONFIG_BUNDLE, CarrierService.this.onLoadConfig(id));
+                result.send(RESULT_OK, data);
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Error in onLoadConfig: " + e.getMessage(), e);
+                result.send(RESULT_ERROR, null);
+            }
         }
     }
 }
diff --git a/android/service/notification/Adjustment.java b/android/service/notification/Adjustment.java
index ce678fc..7348cf6 100644
--- a/android/service/notification/Adjustment.java
+++ b/android/service/notification/Adjustment.java
@@ -56,6 +56,15 @@
     public static final String KEY_GROUP_KEY = "key_group_key";
 
     /**
+     * Data type: int, one of {@link NotificationListenerService.Ranking#USER_SENTIMENT_POSITIVE},
+     * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEUTRAL},
+     * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEGATIVE}. Used to express how
+     * a user feels about notifications in the same {@link android.app.NotificationChannel} as
+     * the notification represented by {@link #getKey()}.
+     */
+    public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+
+    /**
      * Create a notification adjustment.
      *
      * @param pkg The package of the notification.
diff --git a/android/service/notification/NotificationAssistantService.java b/android/service/notification/NotificationAssistantService.java
index d94017c..8e52bfa 100644
--- a/android/service/notification/NotificationAssistantService.java
+++ b/android/service/notification/NotificationAssistantService.java
@@ -16,12 +16,9 @@
 
 package android.service.notification;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
@@ -30,9 +27,9 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+
 import com.android.internal.os.SomeArgs;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -79,7 +76,7 @@
             String snoozeCriterionId);
 
     /**
-     * A notification was posted by an app. Called before alert.
+     * A notification was posted by an app. Called before post.
      *
      * @param sbn the new notification
      * @return an adjustment or null to take no action, within 100ms.
@@ -87,6 +84,34 @@
     abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn);
 
     /**
+     * Implement this method to learn when notifications are removed, how they were interacted with
+     * before removal, and why they were removed.
+     * <p>
+     * This might occur because the user has dismissed the notification using system UI (or another
+     * notification listener) or because the app has withdrawn the notification.
+     * <p>
+     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
+     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
+     * fields such as {@link android.app.Notification#contentView} and
+     * {@link android.app.Notification#largeIcon}. However, all other fields on
+     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
+     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
+     *
+     ** @param sbn A data structure encapsulating at least the original information (tag and id)
+     *            and source (package name) used to post the {@link android.app.Notification} that
+     *            was just removed.
+     * @param rankingMap The current ranking map that can be used to retrieve ranking information
+     *                   for active notifications.
+     * @param stats Stats about how the user interacted with the notification before it was removed.
+     * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
+     */
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            NotificationStats stats, int reason) {
+        onNotificationRemoved(sbn, rankingMap, reason);
+    }
+
+    /**
      * Updates a notification.  N.B. this won’t cause
      * an existing notification to alert, but might allow a future update to
      * this notification to alert.
diff --git a/android/service/notification/NotificationListenerService.java b/android/service/notification/NotificationListenerService.java
index a5223fd..08d3118 100644
--- a/android/service/notification/NotificationListenerService.java
+++ b/android/service/notification/NotificationListenerService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
@@ -265,7 +266,10 @@
     @GuardedBy("mLock")
     private RankingMap mRankingMap;
 
-    private INotificationManager mNoMan;
+    /**
+     * @hide
+     */
+    protected INotificationManager mNoMan;
 
     /**
      * Only valid after a successful call to (@link registerAsService}.
@@ -389,6 +393,18 @@
     }
 
     /**
+     * NotificationStats are not populated for notification listeners, so fall back to
+     * {@link #onNotificationRemoved(StatusBarNotification, RankingMap, int)}.
+     *
+     * @hide
+     */
+    @TestApi
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            NotificationStats stats, int reason) {
+        onNotificationRemoved(sbn, rankingMap, reason);
+    }
+
+    /**
      * Implement this method to learn about when the listener is enabled and connected to
      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
      * at this time.
@@ -1200,7 +1216,7 @@
 
         @Override
         public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
-                NotificationRankingUpdate update, int reason) {
+                NotificationRankingUpdate update, NotificationStats stats, int reason) {
             StatusBarNotification sbn;
             try {
                 sbn = sbnHolder.get();
@@ -1215,6 +1231,7 @@
                 args.arg1 = sbn;
                 args.arg2 = mRankingMap;
                 args.arg3 = reason;
+                args.arg4 = stats;
                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
                         args).sendToTarget();
             }
@@ -1324,6 +1341,26 @@
          * @hide */
         public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
 
+        /**
+         * The user is likely to have a negative reaction to this notification.
+         */
+        public static final int USER_SENTIMENT_NEGATIVE = -1;
+        /**
+         * It is not known how the user will react to this notification.
+         */
+        public static final int USER_SENTIMENT_NEUTRAL = 0;
+        /**
+         * The user is likely to have a positive reaction to this notification.
+         */
+        public static final int USER_SENTIMENT_POSITIVE = 1;
+
+        /** @hide */
+        @IntDef(prefix = { "USER_SENTIMENT_" }, value = {
+                USER_SENTIMENT_NEGATIVE, USER_SENTIMENT_NEUTRAL, USER_SENTIMENT_POSITIVE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface UserSentiment {}
+
         private String mKey;
         private int mRank = -1;
         private boolean mIsAmbient;
@@ -1341,6 +1378,7 @@
         // Notification assistant snooze criteria.
         private ArrayList<SnoozeCriterion> mSnoozeCriteria;
         private boolean mShowBadge;
+        private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
 
         public Ranking() {}
 
@@ -1436,6 +1474,17 @@
         }
 
         /**
+         * Returns how the system thinks the user feels about notifications from the
+         * channel provided by {@link #getChannel()}. You can use this information to expose
+         * controls to help the user block this channel's notifications, if the sentiment is
+         * {@link #USER_SENTIMENT_NEGATIVE}, or emphasize this notification if the sentiment is
+         * {@link #USER_SENTIMENT_POSITIVE}.
+         */
+        public int getUserSentiment() {
+            return mUserSentiment;
+        }
+
+        /**
          * If the {@link NotificationAssistantService} has added people to this notification, then
          * this will be non-null.
          * @hide
@@ -1471,7 +1520,8 @@
                 int visibilityOverride, int suppressedVisualEffects, int importance,
                 CharSequence explanation, String overrideGroupKey,
                 NotificationChannel channel, ArrayList<String> overridePeople,
-                ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
+                ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
+                int userSentiment) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1485,6 +1535,7 @@
             mOverridePeople = overridePeople;
             mSnoozeCriteria = snoozeCriteria;
             mShowBadge = showBadge;
+            mUserSentiment = userSentiment;
         }
 
         /**
@@ -1532,6 +1583,7 @@
         private ArrayMap<String, ArrayList<String>> mOverridePeople;
         private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
         private ArrayMap<String, Boolean> mShowBadge;
+        private ArrayMap<String, Integer> mUserSentiment;
 
         private RankingMap(NotificationRankingUpdate rankingUpdate) {
             mRankingUpdate = rankingUpdate;
@@ -1560,7 +1612,7 @@
                     getVisibilityOverride(key), getSuppressedVisualEffects(key),
                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
                     getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
-                    getShowBadge(key));
+                    getShowBadge(key), getUserSentiment(key));
             return rank >= 0;
         }
 
@@ -1677,6 +1729,17 @@
             return showBadge == null ? false : showBadge.booleanValue();
         }
 
+        private int getUserSentiment(String key) {
+            synchronized (this) {
+                if (mUserSentiment == null) {
+                    buildUserSentimentLocked();
+                }
+            }
+            Integer userSentiment = mUserSentiment.get(key);
+            return userSentiment == null
+                    ? Ranking.USER_SENTIMENT_NEUTRAL : userSentiment.intValue();
+        }
+
         // Locked by 'this'
         private void buildRanksLocked() {
             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1776,6 +1839,15 @@
             }
         }
 
+        // Locked by 'this'
+        private void buildUserSentimentLocked() {
+            Bundle userSentiment = mRankingUpdate.getUserSentiment();
+            mUserSentiment = new ArrayMap<>(userSentiment.size());
+            for (String key : userSentiment.keySet()) {
+                mUserSentiment.put(key, userSentiment.getInt(key));
+            }
+        }
+
         // ----------- Parcelable
 
         @Override
@@ -1835,8 +1907,9 @@
                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
                     RankingMap rankingMap = (RankingMap) args.arg2;
                     int reason = (int) args.arg3;
+                    NotificationStats stats = (NotificationStats) args.arg4;
                     args.recycle();
-                    onNotificationRemoved(sbn, rankingMap, reason);
+                    onNotificationRemoved(sbn, rankingMap, stats, reason);
                 } break;
 
                 case MSG_ON_LISTENER_CONNECTED: {
diff --git a/android/service/notification/NotificationRankingUpdate.java b/android/service/notification/NotificationRankingUpdate.java
index 326b212..6d51db0 100644
--- a/android/service/notification/NotificationRankingUpdate.java
+++ b/android/service/notification/NotificationRankingUpdate.java
@@ -35,12 +35,13 @@
     private final Bundle mOverridePeople;
     private final Bundle mSnoozeCriteria;
     private final Bundle mShowBadge;
+    private final Bundle mUserSentiment;
 
     public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
             Bundle visibilityOverrides, Bundle suppressedVisualEffects,
             int[] importance, Bundle explanation, Bundle overrideGroupKeys,
             Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
-            Bundle showBadge) {
+            Bundle showBadge, Bundle userSentiment) {
         mKeys = keys;
         mInterceptedKeys = interceptedKeys;
         mVisibilityOverrides = visibilityOverrides;
@@ -52,6 +53,7 @@
         mOverridePeople = overridePeople;
         mSnoozeCriteria = snoozeCriteria;
         mShowBadge = showBadge;
+        mUserSentiment = userSentiment;
     }
 
     public NotificationRankingUpdate(Parcel in) {
@@ -67,6 +69,7 @@
         mOverridePeople = in.readBundle();
         mSnoozeCriteria = in.readBundle();
         mShowBadge = in.readBundle();
+        mUserSentiment = in.readBundle();
     }
 
     @Override
@@ -87,6 +90,7 @@
         out.writeBundle(mOverridePeople);
         out.writeBundle(mSnoozeCriteria);
         out.writeBundle(mShowBadge);
+        out.writeBundle(mUserSentiment);
     }
 
     public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -143,4 +147,8 @@
     public Bundle getShowBadge() {
         return mShowBadge;
     }
+
+    public Bundle getUserSentiment() {
+        return mUserSentiment;
+    }
 }
diff --git a/android/service/notification/NotificationStats.java b/android/service/notification/NotificationStats.java
new file mode 100644
index 0000000..76d5328
--- /dev/null
+++ b/android/service/notification/NotificationStats.java
@@ -0,0 +1,256 @@
+/**
+ * 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.notification;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.RemoteInput;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+@TestApi
+@SystemApi
+public final class NotificationStats implements Parcelable {
+
+    private boolean mSeen;
+    private boolean mExpanded;
+    private boolean mDirectReplied;
+    private boolean mSnoozed;
+    private boolean mViewedSettings;
+    private boolean mInteracted;
+
+    /** @hide */
+    @IntDef(prefix = { "DISMISSAL_SURFACE_" }, value = {
+            DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD, DISMISSAL_SHADE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DismissalSurface {}
+
+
+    private @DismissalSurface int mDismissalSurface = DISMISSAL_NOT_DISMISSED;
+
+    /**
+     * Notification has not been dismissed yet.
+     */
+    public static final int DISMISSAL_NOT_DISMISSED = -1;
+    /**
+     * Notification has been dismissed from a {@link NotificationListenerService} or the app
+     * itself.
+     */
+    public static final int DISMISSAL_OTHER = 0;
+    /**
+     * Notification has been dismissed while peeking.
+     */
+    public static final int DISMISSAL_PEEK = 1;
+    /**
+     * Notification has been dismissed from always on display.
+     */
+    public static final int DISMISSAL_AOD = 2;
+    /**
+     * Notification has been dismissed from the notification shade.
+     */
+    public static final int DISMISSAL_SHADE = 3;
+
+    public NotificationStats() {
+    }
+
+    protected NotificationStats(Parcel in) {
+        mSeen = in.readByte() != 0;
+        mExpanded = in.readByte() != 0;
+        mDirectReplied = in.readByte() != 0;
+        mSnoozed = in.readByte() != 0;
+        mViewedSettings = in.readByte() != 0;
+        mInteracted = in.readByte() != 0;
+        mDismissalSurface = in.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByte((byte) (mSeen ? 1 : 0));
+        dest.writeByte((byte) (mExpanded ? 1 : 0));
+        dest.writeByte((byte) (mDirectReplied ? 1 : 0));
+        dest.writeByte((byte) (mSnoozed ? 1 : 0));
+        dest.writeByte((byte) (mViewedSettings ? 1 : 0));
+        dest.writeByte((byte) (mInteracted ? 1 : 0));
+        dest.writeInt(mDismissalSurface);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<NotificationStats> CREATOR = new Creator<NotificationStats>() {
+        @Override
+        public NotificationStats createFromParcel(Parcel in) {
+            return new NotificationStats(in);
+        }
+
+        @Override
+        public NotificationStats[] newArray(int size) {
+            return new NotificationStats[size];
+        }
+    };
+
+    /**
+     * Returns whether the user has seen this notification at least once.
+     */
+    public boolean hasSeen() {
+        return mSeen;
+    }
+
+    /**
+     * Records that the user as seen this notification at least once.
+     */
+    public void setSeen() {
+        mSeen = true;
+    }
+
+    /**
+     * Returns whether the user has expanded this notification at least once.
+     */
+    public boolean hasExpanded() {
+        return mExpanded;
+    }
+
+    /**
+     * Records that the user has expanded this notification at least once.
+     */
+    public void setExpanded() {
+        mExpanded = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has replied to a notification that has a
+     * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply} at
+     * least once.
+     */
+    public boolean hasDirectReplied() {
+        return mDirectReplied;
+    }
+
+    /**
+     * Records that the user has replied to a notification that has a
+     * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply}
+     * at least once.
+     */
+    public void setDirectReplied() {
+        mDirectReplied = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has snoozed this notification at least once.
+     */
+    public boolean hasSnoozed() {
+        return mSnoozed;
+    }
+
+    /**
+     * Records that the user has snoozed this notification at least once.
+     */
+    public void setSnoozed() {
+        mSnoozed = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has viewed the in-shade settings for this notification at least
+     * once.
+     */
+    public boolean hasViewedSettings() {
+        return mViewedSettings;
+    }
+
+    /**
+     * Records that the user has viewed the in-shade settings for this notification at least once.
+     */
+    public void setViewedSettings() {
+        mViewedSettings = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has interacted with this notification beyond having viewed it.
+     */
+    public boolean hasInteracted() {
+        return mInteracted;
+    }
+
+    /**
+     * Returns from which surface the notification was dismissed.
+     */
+    public @DismissalSurface int getDismissalSurface() {
+        return mDismissalSurface;
+    }
+
+    /**
+     * Returns from which surface the notification was dismissed.
+     */
+    public void setDismissalSurface(@DismissalSurface int dismissalSurface) {
+        mDismissalSurface = dismissalSurface;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        NotificationStats that = (NotificationStats) o;
+
+        if (mSeen != that.mSeen) return false;
+        if (mExpanded != that.mExpanded) return false;
+        if (mDirectReplied != that.mDirectReplied) return false;
+        if (mSnoozed != that.mSnoozed) return false;
+        if (mViewedSettings != that.mViewedSettings) return false;
+        if (mInteracted != that.mInteracted) return false;
+        return mDismissalSurface == that.mDismissalSurface;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (mSeen ? 1 : 0);
+        result = 31 * result + (mExpanded ? 1 : 0);
+        result = 31 * result + (mDirectReplied ? 1 : 0);
+        result = 31 * result + (mSnoozed ? 1 : 0);
+        result = 31 * result + (mViewedSettings ? 1 : 0);
+        result = 31 * result + (mInteracted ? 1 : 0);
+        result = 31 * result + mDismissalSurface;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("NotificationStats{");
+        sb.append("mSeen=").append(mSeen);
+        sb.append(", mExpanded=").append(mExpanded);
+        sb.append(", mDirectReplied=").append(mDirectReplied);
+        sb.append(", mSnoozed=").append(mSnoozed);
+        sb.append(", mViewedSettings=").append(mViewedSettings);
+        sb.append(", mInteracted=").append(mInteracted);
+        sb.append(", mDismissalSurface=").append(mDismissalSurface);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/android/service/settings/suggestions/Suggestion.java b/android/service/settings/suggestions/Suggestion.java
index f27cc2e..cfeb7fc 100644
--- a/android/service/settings/suggestions/Suggestion.java
+++ b/android/service/settings/suggestions/Suggestion.java
@@ -16,12 +16,17 @@
 
 package android.service.settings.suggestions;
 
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Data object that has information about a device suggestion.
  *
@@ -30,9 +35,27 @@
 @SystemApi
 public final class Suggestion implements Parcelable {
 
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, value = {
+            FLAG_HAS_BUTTON,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {
+    }
+
+    /**
+     * Flag for suggestion type with a single button
+     */
+    public static final int FLAG_HAS_BUTTON = 1 << 0;
+
     private final String mId;
     private final CharSequence mTitle;
     private final CharSequence mSummary;
+    private final Icon mIcon;
+    @Flags
+    private final int mFlags;
     private final PendingIntent mPendingIntent;
 
     /**
@@ -57,6 +80,22 @@
     }
 
     /**
+     * Optional icon for this suggestion.
+     */
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Optional flags for this suggestion. This will influence UI when rendering suggestion in
+     * different style.
+     */
+    @Flags
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
      * The Intent to launch when the suggestion is activated.
      */
     public PendingIntent getPendingIntent() {
@@ -67,6 +106,8 @@
         mId = builder.mId;
         mTitle = builder.mTitle;
         mSummary = builder.mSummary;
+        mIcon = builder.mIcon;
+        mFlags = builder.mFlags;
         mPendingIntent = builder.mPendingIntent;
     }
 
@@ -74,6 +115,8 @@
         mId = in.readString();
         mTitle = in.readCharSequence();
         mSummary = in.readCharSequence();
+        mIcon = in.readParcelable(Icon.class.getClassLoader());
+        mFlags = in.readInt();
         mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
     }
 
@@ -99,6 +142,8 @@
         dest.writeString(mId);
         dest.writeCharSequence(mTitle);
         dest.writeCharSequence(mSummary);
+        dest.writeParcelable(mIcon, flags);
+        dest.writeInt(mFlags);
         dest.writeParcelable(mPendingIntent, flags);
     }
 
@@ -109,6 +154,9 @@
         private final String mId;
         private CharSequence mTitle;
         private CharSequence mSummary;
+        private Icon mIcon;
+        @Flags
+        private int mFlags;
         private PendingIntent mPendingIntent;
 
         public Builder(String id) {
@@ -135,6 +183,23 @@
         }
 
         /**
+         * Sets icon for the suggestion.
+         */
+        public Builder setIcon(Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets a UI type for this suggestion. This will influence UI when rendering suggestion in
+         * different style.
+         */
+        public Builder setFlags(@Flags int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        /**
          * Sets suggestion intent
          */
         public Builder setPendingIntent(PendingIntent pendingIntent) {
diff --git a/android/slice/Slice.java b/android/slice/Slice.java
new file mode 100644
index 0000000..5768654
--- /dev/null
+++ b/android/slice/Slice.java
@@ -0,0 +1,347 @@
+/*
+ * 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.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;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * 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 {
+
+    /**
+     * @hide
+     */
+    @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
+            HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT})
+    public @interface SliceHint{ }
+
+    /**
+     * Hint that this content is a title of other content in the slice.
+     */
+    public static final String HINT_TITLE       = "title";
+    /**
+     * Hint that all sub-items/sub-slices within this content should be considered
+     * to have {@link #HINT_LIST_ITEM}.
+     */
+    public static final String HINT_LIST        = "list";
+    /**
+     * Hint that this item is part of a list and should be formatted as if is part
+     * of a list.
+     */
+    public static final String HINT_LIST_ITEM   = "list_item";
+    /**
+     * Hint that this content is important and should be larger when displayed if
+     * possible.
+     */
+    public static final String HINT_LARGE       = "large";
+    /**
+     * Hint that this slice contains a number of actions that can be grouped together
+     * in a sort of controls area of the UI.
+     */
+    public static final String HINT_ACTIONS     = "actions";
+    /**
+     * Hint indicating that this item (and its sub-items) are the current selection.
+     */
+    public static final String HINT_SELECTED    = "selected";
+    /**
+     * Hint to indicate that this is a message as part of a communication
+     * sequence in this slice.
+     */
+    public static final String HINT_MESSAGE     = "message";
+    /**
+     * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
+     */
+    public static final String HINT_SOURCE      = "source";
+    /**
+     * Hint that list items within this slice or subslice would appear better
+     * if organized horizontally.
+     */
+    public static final String HINT_HORIZONTAL  = "horizontal";
+    /**
+     * Hint to indicate that this content should not be tinted.
+     */
+    public static final String HINT_NO_TINT     = "no_tint";
+
+    // These two are coming over from prototyping, but we probably don't want in
+    // public API, at least not right now.
+    /**
+     * @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) {
+        mHints = hints;
+        mItems = items.toArray(new SliceItem[items.size()]);
+        mUri = uri;
+    }
+
+    protected Slice(Parcel in) {
+        mHints = in.readStringArray();
+        int n = in.readInt();
+        mItems = new SliceItem[n];
+        for (int i = 0; i < n; i++) {
+            mItems[i] = SliceItem.CREATOR.createFromParcel(in);
+        }
+        mUri = Uri.CREATOR.createFromParcel(in);
+    }
+
+    /**
+     * @return The Uri that this Slice represents.
+     */
+    public Uri getUri() {
+        return mUri;
+    }
+
+    /**
+     * @return All child {@link SliceItem}s that this Slice contains.
+     */
+    public SliceItem[] getItems() {
+        return mItems;
+    }
+
+    /**
+     * @return All hints associated with this Slice.
+     */
+    public @SliceHint String[] getHints() {
+        return mHints;
+    }
+
+    /**
+     * @hide
+     */
+    public SliceItem getPrimaryIcon() {
+        for (SliceItem item : getItems()) {
+            if (item.getType() == TYPE_IMAGE) {
+                return item;
+            }
+            if (!(item.getType() == 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);
+                if (icon != null) return icon;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringArray(mHints);
+        dest.writeInt(mItems.length);
+        for (int i = 0; i < mItems.length; i++) {
+            mItems[i].writeToParcel(dest, flags);
+        }
+        mUri.writeToParcel(dest, 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasHint(@SliceHint String hint) {
+        return ArrayUtils.contains(mHints, hint);
+    }
+
+    /**
+     * A Builder used to construct {@link Slice}s
+     */
+    public static class Builder {
+
+        private final Uri mUri;
+        private ArrayList<SliceItem> mItems = new ArrayList<>();
+        private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+
+        /**
+         * Create a builder which will construct a {@link Slice} for the Given Uri.
+         * @param uri Uri to tag for this slice.
+         */
+        public Builder(@NonNull Uri uri) {
+            mUri = uri;
+        }
+
+        /**
+         * Create a builder for a {@link Slice} that is a sub-slice of the slice
+         * being constructed by the provided builder.
+         * @param parent The builder constructing the parent slice
+         */
+        public Builder(@NonNull Slice.Builder parent) {
+            mUri = parent.mUri.buildUpon().appendPath("_gen")
+                    .appendPath(String.valueOf(mItems.size())).build();
+        }
+
+        /**
+         * Add hints to the Slice being constructed
+         */
+        public Builder addHints(@SliceHint String... hints) {
+            mHints.addAll(Arrays.asList(hints));
+            return this;
+        }
+
+        /**
+         * Add a sub-slice to the slice being constructed
+         */
+        public Builder addSubSlice(@NonNull Slice slice) {
+            mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints()));
+            return this;
+        }
+
+        /**
+         * 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]));
+            return this;
+        }
+
+        /**
+         * Add text to the slice being constructed
+         */
+        public Builder addText(CharSequence text, @SliceHint String... hints) {
+            mItems.add(new SliceItem(text, TYPE_TEXT, hints));
+            return this;
+        }
+
+        /**
+         * Add an image to the slice being constructed
+         */
+        public Builder addIcon(Icon icon, @SliceHint String... hints) {
+            mItems.add(new SliceItem(icon, TYPE_IMAGE, hints));
+            return this;
+        }
+
+        /**
+         * @hide This isn't final
+         */
+        public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
+            mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints));
+            return this;
+        }
+
+        /**
+         * 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));
+            return this;
+        }
+
+        /**
+         * Add a color to the slice being constructed
+         */
+        public Builder addColor(int color, @SliceHint String... hints) {
+            mItems.add(new SliceItem(color, TYPE_COLOR, hints));
+            return this;
+        }
+
+        /**
+         * 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));
+            return this;
+        }
+
+        /**
+         * Construct the slice.
+         */
+        public Slice build() {
+            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+        }
+    }
+
+    public static final Creator<Slice> CREATOR = new Creator<Slice>() {
+        @Override
+        public Slice createFromParcel(Parcel in) {
+            return new Slice(in);
+        }
+
+        @Override
+        public Slice[] newArray(int size) {
+            return new Slice[size];
+        }
+    };
+
+    /**
+     * @hide
+     * @return A string representation of this slice.
+     */
+    public String getString() {
+        return getString("");
+    }
+
+    private String getString(String indent) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < mItems.length; i++) {
+            sb.append(indent);
+            if (mItems[i].getType() == TYPE_SLICE) {
+                sb.append("slice:\n");
+                sb.append(mItems[i].getSlice().getString(indent + "   "));
+            } else if (mItems[i].getType() == TYPE_TEXT) {
+                sb.append("text: ");
+                sb.append(mItems[i].getText());
+                sb.append("\n");
+            } else {
+                sb.append(SliceItem.typeToString(mItems[i].getType()));
+                sb.append("\n");
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/android/slice/SliceItem.java b/android/slice/SliceItem.java
new file mode 100644
index 0000000..2827ab9
--- /dev/null
+++ b/android/slice/SliceItem.java
@@ -0,0 +1,344 @@
+/*
+ * 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.slice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+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;
+
+
+/**
+ * A SliceItem is a single unit in the tree structure of a {@link Slice}.
+ *
+ * A SliceItem a piece of content and some hints about what that content
+ * means or how it should be displayed. The types of content can be:
+ * <li>{@link #TYPE_SLICE}</li>
+ * <li>{@link #TYPE_TEXT}</li>
+ * <li>{@link #TYPE_IMAGE}</li>
+ * <li>{@link #TYPE_ACTION}</li>
+ * <li>{@link #TYPE_COLOR}</li>
+ * <li>{@link #TYPE_TIMESTAMP}</li>
+ * <li>{@link #TYPE_REMOTE_INPUT}</li>
+ *
+ * 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 {
+
+    /**
+     * @hide
+     */
+    @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
+            TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
+    public @interface SliceType {}
+
+    /**
+     * A {@link SliceItem} that contains a {@link Slice}
+     */
+    public static final int TYPE_SLICE        = 1;
+    /**
+     * A {@link SliceItem} that contains a {@link CharSequence}
+     */
+    public static final int TYPE_TEXT         = 2;
+    /**
+     * A {@link SliceItem} that contains an {@link Icon}
+     */
+    public static final int TYPE_IMAGE        = 3;
+    /**
+     * A {@link SliceItem} that contains a {@link PendingIntent}
+     *
+     * Note: Actions contain 2 pieces of data, In addition to the pending intent, the
+     * item contains a {@link Slice} that the action applies to.
+     */
+    public static final int TYPE_ACTION       = 4;
+    /**
+     * @hide This isn't final
+     */
+    public static final int TYPE_REMOTE_VIEW  = 5;
+    /**
+     * A {@link SliceItem} that contains a Color int.
+     */
+    public static final int TYPE_COLOR        = 6;
+    /**
+     * A {@link SliceItem} that contains a timestamp.
+     */
+    public static final int TYPE_TIMESTAMP    = 8;
+    /**
+     * A {@link SliceItem} that contains a {@link RemoteInput}.
+     */
+    public static final int TYPE_REMOTE_INPUT = 9;
+
+    /**
+     * @hide
+     */
+    protected @SliceHint String[] mHints;
+    private final int mType;
+    private final Object mObj;
+
+    /**
+     * @hide
+     */
+    public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) {
+        mHints = hints;
+        mType = type;
+        mObj = obj;
+    }
+
+    /**
+     * @hide
+     */
+    public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) {
+        this(new Pair<>(intent, slice), type, hints);
+    }
+
+    /**
+     * Gets all hints associated with this SliceItem.
+     * @return Array of hints.
+     */
+    public @NonNull @SliceHint String[] getHints() {
+        return mHints;
+    }
+
+    /**
+     * @hide
+     */
+    public void addHint(@SliceHint String hint) {
+        mHints = ArrayUtils.appendElement(String.class, mHints, hint);
+    }
+
+    /**
+     * @hide
+     */
+    public void removeHint(String hint) {
+        ArrayUtils.removeElement(String.class, mHints, hint);
+    }
+
+    public @SliceType int getType() {
+        return mType;
+    }
+
+    /**
+     * @return The text held by this {@link #TYPE_TEXT} SliceItem
+     */
+    public CharSequence getText() {
+        return (CharSequence) mObj;
+    }
+
+    /**
+     * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
+     */
+    public Icon getIcon() {
+        return (Icon) mObj;
+    }
+
+    /**
+     * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
+     */
+    public PendingIntent getAction() {
+        return ((Pair<PendingIntent, Slice>) mObj).first;
+    }
+
+    /**
+     * @hide This isn't final
+     */
+    public RemoteViews getRemoteView() {
+        return (RemoteViews) mObj;
+    }
+
+    /**
+     * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
+     */
+    public RemoteInput getRemoteInput() {
+        return (RemoteInput) mObj;
+    }
+
+    /**
+     * @return The color held by this {@link #TYPE_COLOR} SliceItem
+     */
+    public int getColor() {
+        return (Integer) mObj;
+    }
+
+    /**
+     * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
+     */
+    public Slice getSlice() {
+        if (getType() == TYPE_ACTION) {
+            return ((Pair<PendingIntent, Slice>) mObj).second;
+        }
+        return (Slice) mObj;
+    }
+
+    /**
+     * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
+     */
+    public long getTimestamp() {
+        return (Long) mObj;
+    }
+
+    /**
+     * @param hint The hint to check for
+     * @return true if this item contains the given hint
+     */
+    public boolean hasHint(@SliceHint String hint) {
+        return ArrayUtils.contains(mHints, hint);
+    }
+
+    /**
+     * @hide
+     */
+    public SliceItem(Parcel in) {
+        mHints = in.readStringArray();
+        mType = in.readInt();
+        mObj = readObj(mType, in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringArray(mHints);
+        dest.writeInt(mType);
+        writeObj(dest, flags, mObj, mType);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasHints(@SliceHint String[] hints) {
+        if (hints == null) return true;
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasAnyHints(@SliceHint String[] hints) {
+        if (hints == null) return false;
+        for (String hint : hints) {
+            if (ArrayUtils.contains(mHints, hint)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void writeObj(Parcel dest, int flags, Object obj, int type) {
+        switch (type) {
+            case TYPE_SLICE:
+            case TYPE_REMOTE_VIEW:
+            case TYPE_IMAGE:
+            case TYPE_REMOTE_INPUT:
+                ((Parcelable) obj).writeToParcel(dest, flags);
+                break;
+            case TYPE_ACTION:
+                ((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
+                ((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
+                break;
+            case TYPE_TEXT:
+                TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
+                break;
+            case TYPE_COLOR:
+                dest.writeInt((Integer) mObj);
+                break;
+            case TYPE_TIMESTAMP:
+                dest.writeLong((Long) mObj);
+                break;
+        }
+    }
+
+    private static Object readObj(int type, Parcel in) {
+        switch (type) {
+            case TYPE_SLICE:
+                return Slice.CREATOR.createFromParcel(in);
+            case TYPE_TEXT:
+                return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            case TYPE_IMAGE:
+                return Icon.CREATOR.createFromParcel(in);
+            case TYPE_ACTION:
+                return new Pair<PendingIntent, Slice>(
+                        PendingIntent.CREATOR.createFromParcel(in),
+                        Slice.CREATOR.createFromParcel(in));
+            case TYPE_REMOTE_VIEW:
+                return RemoteViews.CREATOR.createFromParcel(in);
+            case TYPE_COLOR:
+                return in.readInt();
+            case TYPE_TIMESTAMP:
+                return in.readLong();
+            case TYPE_REMOTE_INPUT:
+                return RemoteInput.CREATOR.createFromParcel(in);
+        }
+        throw new RuntimeException("Unsupported type " + type);
+    }
+
+    public static final Creator<SliceItem> CREATOR = new Creator<SliceItem>() {
+        @Override
+        public SliceItem createFromParcel(Parcel in) {
+            return new SliceItem(in);
+        }
+
+        @Override
+        public SliceItem[] newArray(int size) {
+            return new SliceItem[size];
+        }
+    };
+
+    /**
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_SLICE:
+                return "Slice";
+            case TYPE_TEXT:
+                return "Text";
+            case TYPE_IMAGE:
+                return "Image";
+            case TYPE_ACTION:
+                return "Action";
+            case TYPE_REMOTE_VIEW:
+                return "RemoteView";
+            case TYPE_COLOR:
+                return "Color";
+            case TYPE_TIMESTAMP:
+                return "Timestamp";
+            case TYPE_REMOTE_INPUT:
+                return "RemoteInput";
+        }
+        return "Unrecognized type: " + type;
+    }
+}
diff --git a/android/slice/SliceProvider.java b/android/slice/SliceProvider.java
new file mode 100644
index 0000000..4e21371
--- /dev/null
+++ b/android/slice/SliceProvider.java
@@ -0,0 +1,156 @@
+/*
+ * 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.slice;
+
+import android.Manifest.permission;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A SliceProvider allows app to provide content to be displayed in system
+ * spaces. This content is templated and can contain actions, and the behavior
+ * of how it is surfaced is specific to the system surface.
+ *
+ * <p>Slices are not currently live content. They are bound once and shown to the
+ * user. If the content changes due to a callback from user interaction, then
+ * {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+ * should be used to notify the system.</p>
+ *
+ * <p>The provider needs to be declared in the manifest to provide the authority
+ * for the app. The authority for most slices is expected to match the package
+ * of the application.</p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ *     android:name="com.android.mypkg.MySliceProvider"
+ *     android:authorities="com.android.mypkg" />}
+ * </pre>
+ *
+ * @see Slice
+ * @hide
+ */
+public abstract class SliceProvider extends ContentProvider {
+
+    private static final String TAG = "SliceProvider";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_BIND_URI = "slice_uri";
+    /**
+     * @hide
+     */
+    public static final String METHOD_SLICE = "bind_slice";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_SLICE = "slice";
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Implemented to create a slice. Will be called on the main thread.
+     * @see {@link Slice}.
+     */
+    public abstract Slice onBindSlice(Uri sliceUri);
+
+    @Override
+    public final int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        if (DEBUG) Log.d(TAG, "update " + uri);
+        return 0;
+    }
+
+    @Override
+    public final int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (DEBUG) Log.d(TAG, "delete " + uri);
+        return 0;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, String selection, String[]
+            selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+            CancellationSignal cancellationSignal) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Uri insert(Uri uri, ContentValues values) {
+        if (DEBUG) Log.d(TAG, "insert " + uri);
+        return null;
+    }
+
+    @Override
+    public final String getType(Uri uri) {
+        if (DEBUG) Log.d(TAG, "getType " + uri);
+        return null;
+    }
+
+    @Override
+    public final 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");
+            Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+
+            Slice s = handleBindSlice(uri);
+            Bundle b = new Bundle();
+            b.putParcelable(EXTRA_SLICE, s);
+            return b;
+        }
+        return super.call(method, arg, extras);
+    }
+
+    private Slice handleBindSlice(Uri sliceUri) {
+        Slice[] output = new Slice[1];
+        CountDownLatch latch = new CountDownLatch(1);
+        Handler mainHandler = new Handler(Looper.getMainLooper());
+        mainHandler.post(() -> {
+            output[0] = onBindSlice(sliceUri);
+            latch.countDown();
+        });
+        try {
+            latch.await();
+            return output[0];
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/android/slice/SliceQuery.java b/android/slice/SliceQuery.java
new file mode 100644
index 0000000..d99b26a
--- /dev/null
+++ b/android/slice/SliceQuery.java
@@ -0,0 +1,151 @@
+/*
+ * 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.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;
+import java.util.Queue;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * A bunch of utilities for searching the contents of a slice.
+ * @hide
+ */
+public class SliceQuery {
+    private static final String TAG = "SliceQuery";
+
+    /**
+     * @hide
+     */
+    public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
+        SliceItem ret = null;
+        while (ret == null && list.size() != 0) {
+            SliceItem remove = list.remove(0);
+            if (!contains(container, remove)) {
+                ret = remove;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * @hide
+     */
+    private static boolean contains(SliceItem container, SliceItem item) {
+        if (container == null || item == null) return false;
+        return stream(container).filter(s -> (s == item)).findAny().isPresent();
+    }
+
+    /**
+     * @hide
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type) {
+        return findAll(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+        return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     * @hide
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+            String[] nonHints) {
+        return stream(s).filter(item -> (type == -1 || item.getType() == type)
+                && (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(Slice s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(SliceItem s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     * @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);
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
+        return stream(s).filter(item -> (item.getType() == type || type == -1)
+                && (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
+    }
+
+    /**
+     * @hide
+     */
+    public static Stream<SliceItem> stream(SliceItem slice) {
+        Queue<SliceItem> items = new LinkedList();
+        items.add(slice);
+        Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
+            @Override
+            public boolean hasNext() {
+                return items.size() != 0;
+            }
+
+            @Override
+            public SliceItem next() {
+                SliceItem item = items.poll();
+                if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) {
+                    items.addAll(Arrays.asList(item.getSlice().getItems()));
+                }
+                return item;
+            }
+        };
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+    }
+}
diff --git a/android/slice/views/ActionRow.java b/android/slice/views/ActionRow.java
new file mode 100644
index 0000000..93e9c03
--- /dev/null
+++ b/android/slice/views/ActionRow.java
@@ -0,0 +1,201 @@
+/*
+ * 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.slice.views;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+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;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class ActionRow extends FrameLayout {
+
+    private static final int MAX_ACTIONS = 5;
+    private final int mSize;
+    private final int mIconPadding;
+    private final LinearLayout mActionsGroup;
+    private final boolean mFullActions;
+    private int mColor = Color.BLACK;
+
+    public ActionRow(Context context, boolean fullActions) {
+        super(context);
+        mFullActions = fullActions;
+        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
+                context.getResources().getDisplayMetrics());
+        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
+                context.getResources().getDisplayMetrics());
+        mActionsGroup = new LinearLayout(context);
+        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
+        mActionsGroup.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        addView(mActionsGroup);
+    }
+
+    private void setColor(int color) {
+        mColor = color;
+        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+            View view = mActionsGroup.getChildAt(i);
+            SliceItem item = (SliceItem) view.getTag();
+            boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
+            if (tint) {
+                ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
+            }
+        }
+    }
+
+    private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
+        ImageView imageView = new ImageView(getContext());
+        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+        imageView.setScaleType(ScaleType.FIT_CENTER);
+        imageView.setImageIcon(icon);
+        if (allowTint) {
+            imageView.setImageTintList(ColorStateList.valueOf(mColor));
+        }
+        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+        imageView.setTag(image);
+        addAction(imageView);
+        return imageView;
+    }
+
+    /**
+     * Set the actions and color for this action row.
+     */
+    public void setActions(SliceItem actionRow, SliceItem defColor) {
+        removeAllViews();
+        mActionsGroup.removeAllViews();
+        addView(mActionsGroup);
+
+        SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
+        if (color == null) {
+            color = defColor;
+        }
+        if (color != null) {
+            setColor(color.getColor());
+        }
+        SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
+            if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+                return;
+            }
+            SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
+            if (image == null) {
+                return;
+            }
+            boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
+            SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
+            if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+                addAction(image.getIcon(), tint, image).setOnClickListener(
+                        v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
+                createRemoteInputView(mColor, getContext());
+            } else {
+                addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                action.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+            }
+        });
+        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
+    }
+
+    private void addAction(View child) {
+        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
+    }
+
+    private void createRemoteInputView(int color, Context context) {
+        View riv = RemoteInputView.inflate(context, this);
+        riv.setVisibility(View.INVISIBLE);
+        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        riv.setBackgroundColor(color);
+    }
+
+    private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
+            RemoteInput input) {
+        if (input == null) {
+            return false;
+        }
+
+        ViewParent p = view.getParent().getParent();
+        RemoteInputView riv = null;
+        while (p != null) {
+            if (p instanceof View) {
+                View pv = (View) p;
+                riv = findRemoteInputView(pv);
+                if (riv != null) {
+                    break;
+                }
+            }
+            p = p.getParent();
+        }
+        if (riv == null) {
+            return false;
+        }
+
+        int width = view.getWidth();
+        if (view instanceof TextView) {
+            // Center the reveal on the text which might be off-center from the TextView
+            TextView tv = (TextView) view;
+            if (tv.getLayout() != null) {
+                int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                width = Math.min(width, innerWidth);
+            }
+        }
+        int cx = view.getLeft() + width / 2;
+        int cy = view.getTop() + view.getHeight() / 2;
+        int w = riv.getWidth();
+        int h = riv.getHeight();
+        int r = Math.max(
+                Math.max(cx + cy, cx + (h - cy)),
+                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+        riv.setRevealParameters(cx, cy, r);
+        riv.setPendingIntent(pendingIntent);
+        riv.setRemoteInput(new RemoteInput[] {
+                input
+        }, input);
+        riv.focusAnimated();
+        return true;
+    }
+
+    private RemoteInputView findRemoteInputView(View v) {
+        if (v == null) {
+            return null;
+        }
+        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+    }
+}
diff --git a/android/slice/views/GridView.java b/android/slice/views/GridView.java
new file mode 100644
index 0000000..18a90f7
--- /dev/null
+++ b/android/slice/views/GridView.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 android.slice.views;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+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;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class GridView extends LinearLayout implements SliceListView {
+
+    private static final String TAG = "GridView";
+
+    private static final int MAX_IMAGES = 3;
+    private static final int MAX_ALL = 5;
+    private boolean mIsAllImages;
+
+    public GridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mIsAllImages) {
+            int width = MeasureSpec.getSize(widthMeasureSpec);
+            int height = width / getChildCount();
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
+                    height);
+            getLayoutParams().height = height;
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).getLayoutParams().height = height;
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        mIsAllImages = true;
+        removeAllViews();
+        int total = 1;
+        if (slice.getType() == SliceItem.TYPE_SLICE) {
+            SliceItem[] items = slice.getSlice().getItems();
+            total = items.length;
+            for (int i = 0; i < total; i++) {
+                SliceItem item = items[i];
+                if (isFull()) {
+                    continue;
+                }
+                if (!addItem(item)) {
+                    mIsAllImages = false;
+                }
+            }
+        } else {
+            if (!isFull()) {
+                if (!addItem(slice)) {
+                    mIsAllImages = false;
+                }
+            }
+        }
+        if (total > getChildCount() && mIsAllImages) {
+            addExtraCount(total - getChildCount());
+        }
+    }
+
+    private void addExtraCount(int numExtra) {
+        View last = getChildAt(getChildCount() - 1);
+        FrameLayout frame = new FrameLayout(getContext());
+        frame.setLayoutParams(last.getLayoutParams());
+
+        removeView(last);
+        frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        TextView v = new TextView(getContext());
+        v.setTextColor(Color.WHITE);
+        v.setBackgroundColor(0x4d000000);
+        v.setText(getResources().getString(R.string.slice_more_content, numExtra));
+        v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+        v.setGravity(Gravity.CENTER);
+        frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        addView(frame);
+    }
+
+    private boolean isFull() {
+        return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
+    }
+
+    /**
+     * Returns true if this item is just an image.
+     */
+    private boolean addItem(SliceItem item) {
+        if (item.getType() == SliceItem.TYPE_IMAGE) {
+            ImageView v = new ImageView(getContext());
+            v.setImageIcon(item.getIcon());
+            v.setScaleType(ScaleType.CENTER_CROP);
+            addView(v, new LayoutParams(0, MATCH_PARENT, 1));
+            return true;
+        } else {
+            LinearLayout v = new LinearLayout(getContext());
+            int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    12, getContext().getResources().getDisplayMetrics());
+            v.setPadding(0, s, 0, 0);
+            v.setOrientation(LinearLayout.VERTICAL);
+            v.setGravity(Gravity.CENTER_HORIZONTAL);
+            // 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.forEach(i -> {
+                Context context = getContext();
+                switch (i.getType()) {
+                    case SliceItem.TYPE_TEXT:
+                        boolean title = false;
+                        if ((item.hasAnyHints(new String[] {
+                                Slice.HINT_LARGE, Slice.HINT_TITLE
+                        }))) {
+                            title = true;
+                        }
+                        TextView tv = (TextView) LayoutInflater.from(context).inflate(
+                                title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
+                        tv.setText(i.getText());
+                        v.addView(tv);
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        ImageView iv = new ImageView(context);
+                        iv.setImageIcon(i.getIcon());
+                        if (item.hasHint(Slice.HINT_LARGE)) {
+                            iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                        } else {
+                            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                                    48, context.getResources().getDisplayMetrics());
+                            iv.setLayoutParams(new LayoutParams(size, size));
+                        }
+                        v.addView(iv);
+                        break;
+                    case SliceItem.TYPE_REMOTE_VIEW:
+                        v.addView(i.getRemoteView().apply(context, v));
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        // TODO: Support color to tint stuff here.
+                        break;
+                }
+            });
+            addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
+            return false;
+        }
+    }
+}
diff --git a/android/slice/views/LargeSliceAdapter.java b/android/slice/views/LargeSliceAdapter.java
new file mode 100644
index 0000000..e77a1b2
--- /dev/null
+++ b/android/slice/views/LargeSliceAdapter.java
@@ -0,0 +1,224 @@
+/*
+ * 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.slice.views;
+
+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;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.RecyclerView;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @hide
+ */
+public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
+
+    public static final int TYPE_DEFAULT       = 1;
+    public static final int TYPE_HEADER        = 2;
+    public static final int TYPE_GRID          = 3;
+    public static final int TYPE_MESSAGE       = 4;
+    public static final int TYPE_MESSAGE_LOCAL = 5;
+    public static final int TYPE_REMOTE_VIEWS  = 6;
+
+    private final IdGenerator mIdGen = new IdGenerator();
+    private final Context mContext;
+    private List<SliceWrapper> mSlices = new ArrayList<>();
+    private SliceItem mColor;
+
+    public LargeSliceAdapter(Context context) {
+        mContext = context;
+        setHasStableIds(true);
+    }
+
+    /**
+     * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
+     */
+    public void setSliceItems(List<SliceItem> slices, SliceItem color) {
+        mColor = color;
+        mIdGen.resetUsage();
+        mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
+                .collect(Collectors.toList());
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = inflateforType(viewType);
+        v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        return new SliceViewHolder(v);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSlices.get(position).mType;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mSlices.get(position).mId;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSlices.size();
+    }
+
+    @Override
+    public void onBindViewHolder(SliceViewHolder holder, int position) {
+        SliceWrapper slice = mSlices.get(position);
+        if (holder.mSliceView != null) {
+            holder.mSliceView.setColor(mColor);
+            holder.mSliceView.setSliceItem(slice.mItem);
+        } else if (slice.mType == TYPE_REMOTE_VIEWS) {
+            FrameLayout frame = (FrameLayout) holder.itemView;
+            frame.removeAllViews();
+            frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
+        }
+    }
+
+    private View inflateforType(int viewType) {
+        switch (viewType) {
+            case TYPE_REMOTE_VIEWS:
+                return new FrameLayout(mContext);
+            case TYPE_GRID:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
+            case TYPE_MESSAGE:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
+            case TYPE_MESSAGE_LOCAL:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
+        }
+        return new SmallTemplateView(mContext);
+    }
+
+    protected static class SliceWrapper {
+        private final SliceItem mItem;
+        private final int mType;
+        private final long mId;
+
+        public SliceWrapper(SliceItem item, IdGenerator idGen) {
+            mItem = item;
+            mType = getType(item);
+            mId = idGen.getId(item);
+        }
+
+        public static int getType(SliceItem item) {
+            if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
+                return TYPE_REMOTE_VIEWS;
+            }
+            if (item.hasHint(Slice.HINT_MESSAGE)) {
+                // TODO: Better way to determine me or not? Something more like Messaging style.
+                if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
+                    return TYPE_MESSAGE;
+                } else {
+                    return TYPE_MESSAGE_LOCAL;
+                }
+            }
+            if (item.hasHint(Slice.HINT_HORIZONTAL)) {
+                return TYPE_GRID;
+            }
+            return TYPE_DEFAULT;
+        }
+    }
+
+    /**
+     * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+     */
+    public static class SliceViewHolder extends ViewHolder {
+        public final SliceListView mSliceView;
+
+        public SliceViewHolder(View itemView) {
+            super(itemView);
+            mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
+        }
+    }
+
+    /**
+     * View slices being displayed in {@link LargeSliceAdapter}.
+     */
+    public interface SliceListView {
+        /**
+         * Set the slice item for this view.
+         */
+        void setSliceItem(SliceItem slice);
+
+        /**
+         * Set the color for the items in this view.
+         */
+        default void setColor(SliceItem color) {
+
+        }
+    }
+
+    private static class IdGenerator {
+        private long mNextLong = 0;
+        private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
+        private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
+
+        public long getId(SliceItem item) {
+            String str = genString(item);
+            if (!mCurrentIds.containsKey(str)) {
+                mCurrentIds.put(str, mNextLong++);
+            }
+            long id = mCurrentIds.get(str);
+            int index = mUsedIds.getOrDefault(str, 0);
+            mUsedIds.put(str, index + 1);
+            return id + index * 10000;
+        }
+
+        private String genString(SliceItem item) {
+            StringBuilder builder = new StringBuilder();
+            SliceQuery.stream(item).forEach(i -> {
+                builder.append(i.getType());
+                i.removeHint(Slice.HINT_SELECTED);
+                builder.append(i.getHints());
+                switch (i.getType()) {
+                    case SliceItem.TYPE_REMOTE_VIEW:
+                        builder.append(i.getRemoteView());
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        builder.append(i.getIcon());
+                        break;
+                    case SliceItem.TYPE_TEXT:
+                        builder.append(i.getText());
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        builder.append(i.getColor());
+                        break;
+                }
+            });
+            return builder.toString();
+        }
+
+        public void resetUsage() {
+            mUsedIds.clear();
+        }
+    }
+}
diff --git a/android/slice/views/LargeTemplateView.java b/android/slice/views/LargeTemplateView.java
new file mode 100644
index 0000000..d53e8fc
--- /dev/null
+++ b/android/slice/views/LargeTemplateView.java
@@ -0,0 +1,116 @@
+/*
+ * 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.slice.views;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+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;
+
+/**
+ * @hide
+ */
+public class LargeTemplateView extends SliceModeView {
+    private final LargeSliceAdapter mAdapter;
+    private final RecyclerView mRecyclerView;
+    private final int mDefaultHeight;
+    private final int mMaxHeight;
+    private Slice mSlice;
+
+    public LargeTemplateView(Context context) {
+        super(context);
+
+        mRecyclerView = new RecyclerView(getContext());
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        mAdapter = new LargeSliceAdapter(context);
+        mRecyclerView.setAdapter(mAdapter);
+        addView(mRecyclerView);
+        int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
+                getResources().getDisplayMetrics());
+        setLayoutParams(new LayoutParams(width, WRAP_CONTENT));
+        mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+        mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_LARGE;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mRecyclerView.getMeasuredHeight() > mMaxHeight
+                || mSlice.hasHint(Slice.HINT_PARTIAL)) {
+            mRecyclerView.getLayoutParams().height = mDefaultHeight;
+        } else {
+            mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        mSlice = slice;
+        List<SliceItem> items = new ArrayList<>();
+        boolean[] hasHeader = new boolean[1];
+        if (slice.hasHint(Slice.HINT_LIST)) {
+            addList(slice, items);
+        } else {
+            Arrays.asList(slice.getItems()).forEach(item -> {
+                if (item.hasHint(Slice.HINT_ACTIONS)) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_COLOR) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_SLICE
+                        && item.hasHint(Slice.HINT_LIST)) {
+                    addList(item.getSlice(), items);
+                } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
+                    items.add(item);
+                } else if (!hasHeader[0]) {
+                    hasHeader[0] = true;
+                    items.add(0, item);
+                } else {
+                    item.addHint(Slice.HINT_LIST_ITEM);
+                    items.add(item);
+                }
+            });
+        }
+        mAdapter.setSliceItems(items, color);
+    }
+
+    private void addList(Slice slice, List<SliceItem> items) {
+        List<SliceItem> sliceItems = Arrays.asList(slice.getItems());
+        sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
+        items.addAll(sliceItems);
+    }
+}
diff --git a/android/slice/views/MessageView.java b/android/slice/views/MessageView.java
new file mode 100644
index 0000000..7b03e0b
--- /dev/null
+++ b/android/slice/views/MessageView.java
@@ -0,0 +1,77 @@
+/*
+ * 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.slice.views;
+
+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;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class MessageView extends LinearLayout implements SliceListView {
+
+    private TextView mDetails;
+    private ImageView mIcon;
+
+    public MessageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDetails = findViewById(android.R.id.summary);
+        mIcon = findViewById(android.R.id.icon);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
+        if (source != null) {
+            final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    24, getContext().getResources().getDisplayMetrics());
+            // TODO try and turn this into a drawable
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            Drawable d = source.getIcon().loadDrawable(getContext());
+            d.setBounds(0, 0, iconSize, iconSize);
+            d.draw(iconCanvas);
+            mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
+        }
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
+            if (builder.length() != 0) {
+                builder.append('\n');
+            }
+            builder.append(text.getText());
+        });
+        mDetails.setText(builder.toString());
+    }
+
+}
diff --git a/android/slice/views/RemoteInputView.java b/android/slice/views/RemoteInputView.java
new file mode 100644
index 0000000..a29bb5c
--- /dev/null
+++ b/android/slice/views/RemoteInputView.java
@@ -0,0 +1,445 @@
+/*
+ * 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.slice.views;
+
+import android.animation.Animator;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
+
+    private static final String TAG = "RemoteInput";
+
+    /**
+     * A marker object that let's us easily find views of this class.
+     */
+    public static final Object VIEW_TAG = new Object();
+
+    private RemoteEditText mEditText;
+    private ImageButton mSendButton;
+    private ProgressBar mProgressBar;
+    private PendingIntent mPendingIntent;
+    private RemoteInput[] mRemoteInputs;
+    private RemoteInput mRemoteInput;
+
+    private int mRevealCx;
+    private int mRevealCy;
+    private int mRevealR;
+    private boolean mResetting;
+
+    public RemoteInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mProgressBar = findViewById(R.id.remote_input_progress);
+        mSendButton = findViewById(R.id.remote_input_send);
+        mSendButton.setOnClickListener(this);
+
+        mEditText = (RemoteEditText) getChildAt(0);
+        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                final boolean isSoftImeEvent = event == null
+                        && (actionId == EditorInfo.IME_ACTION_DONE
+                                || actionId == EditorInfo.IME_ACTION_NEXT
+                                || actionId == EditorInfo.IME_ACTION_SEND);
+                final boolean isKeyboardEnterKey = event != null
+                        && KeyEvent.isConfirmKey(event.getKeyCode())
+                        && event.getAction() == KeyEvent.ACTION_DOWN;
+
+                if (isSoftImeEvent || isKeyboardEnterKey) {
+                    if (mEditText.length() > 0) {
+                        sendRemoteInput();
+                    }
+                    // Consume action to prevent IME from closing.
+                    return true;
+                }
+                return false;
+            }
+        });
+        mEditText.addTextChangedListener(this);
+        mEditText.setInnerFocusable(false);
+        mEditText.mRemoteInputView = this;
+    }
+
+    private void sendRemoteInput() {
+        Bundle results = new Bundle();
+        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
+                results);
+
+        mEditText.setEnabled(false);
+        mSendButton.setVisibility(INVISIBLE);
+        mProgressBar.setVisibility(VISIBLE);
+        mEditText.mShowImeOnInputConnection = false;
+
+        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
+        // will reset the throttling for this package.
+        // Strictly speaking, the intent receiver may be different from the intent creator,
+        // but that's an edge case, and also because we can't always know which package will receive
+        // an intent, so we just reset for the creator.
+        getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+                mPendingIntent.getCreatorPackage(),
+                getContext().getUserId());
+
+        try {
+            mPendingIntent.send(mContext, 0, fillInIntent);
+            reset();
+        } catch (PendingIntent.CanceledException e) {
+            Log.i(TAG, "Unable to send remote input result", e);
+            Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
+                    Toast.LENGTH_SHORT).show();
+            reset();
+        }
+    }
+
+    /**
+     * Creates a remote input view.
+     */
+    public static RemoteInputView inflate(Context context, ViewGroup root) {
+        RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
+                R.layout.slice_remote_input, root, false);
+        v.setTag(VIEW_TAG);
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSendButton) {
+            sendRemoteInput();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // We never want for a touch to escape to an outer view or one we covered.
+        return true;
+    }
+
+    private void onDefocus() {
+        setVisibility(INVISIBLE);
+    }
+
+    /**
+     * Set the pending intent for remote input.
+     */
+    public void setPendingIntent(PendingIntent pendingIntent) {
+        mPendingIntent = pendingIntent;
+    }
+
+    /**
+     * Set the remote inputs for this view.
+     */
+    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+        mRemoteInputs = remoteInputs;
+        mRemoteInput = remoteInput;
+        mEditText.setHint(mRemoteInput.getLabel());
+    }
+
+    /**
+     * Focuses the remote input view.
+     */
+    public void focusAnimated() {
+        if (getVisibility() != VISIBLE) {
+            Animator animator = ViewAnimationUtils.createCircularReveal(
+                    this, mRevealCx, mRevealCy, 0, mRevealR);
+            animator.setDuration(200);
+            animator.start();
+        }
+        focus();
+    }
+
+    private void focus() {
+        setVisibility(VISIBLE);
+        mEditText.setInnerFocusable(true);
+        mEditText.mShowImeOnInputConnection = true;
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.requestFocus();
+        updateSendButton();
+    }
+
+    private void reset() {
+        mResetting = true;
+
+        mEditText.getText().clear();
+        mEditText.setEnabled(true);
+        mSendButton.setVisibility(VISIBLE);
+        mProgressBar.setVisibility(INVISIBLE);
+        updateSendButton();
+        onDefocus();
+
+        mResetting = false;
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mResetting && child == mEditText) {
+            // Suppress text events if it happens during resetting. Ideally this would be
+            // suppressed by the text view not being shown, but that doesn't work here because it
+            // needs to stay visible for the animation.
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    private void updateSendButton() {
+        mSendButton.setEnabled(mEditText.getText().length() != 0);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        updateSendButton();
+    }
+
+    /**
+     * Tries to find an action that matches the current pending intent of this view and updates its
+     * state to that of the found action
+     *
+     * @return true if a matching action was found, false otherwise
+     */
+    public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
+        if (mPendingIntent == null || actions == null) {
+            return false;
+        }
+        Intent current = mPendingIntent.getIntent();
+        if (current == null) {
+            return false;
+        }
+
+        for (Notification.Action a : actions) {
+            RemoteInput[] inputs = a.getRemoteInputs();
+            if (a.actionIntent == null || inputs == null) {
+                continue;
+            }
+            Intent candidate = a.actionIntent.getIntent();
+            if (!current.filterEquals(candidate)) {
+                continue;
+            }
+
+            RemoteInput input = null;
+            for (RemoteInput i : inputs) {
+                if (i.getAllowFreeFormInput()) {
+                    input = i;
+                }
+            }
+            if (input == null) {
+                continue;
+            }
+            setPendingIntent(a.actionIntent);
+            setRemoteInput(inputs, input);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public void setRevealParameters(int cx, int cy, int r) {
+        mRevealCx = cx;
+        mRevealCy = cy;
+        mRevealR = r;
+    }
+
+    @Override
+    public void dispatchStartTemporaryDetach() {
+        super.dispatchStartTemporaryDetach();
+        // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+        // won't lose IME focus.
+        detachViewFromParent(mEditText);
+    }
+
+    @Override
+    public void dispatchFinishTemporaryDetach() {
+        if (isAttachedToWindow()) {
+            attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+        } else {
+            removeDetachedView(mEditText, false /* animate */);
+        }
+        super.dispatchFinishTemporaryDetach();
+    }
+
+    /**
+     * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
+     * whenever the user navigates away from it or it becomes invisible.
+     */
+    public static class RemoteEditText extends EditText {
+
+        private final Drawable mBackground;
+        private RemoteInputView mRemoteInputView;
+        boolean mShowImeOnInputConnection;
+
+        public RemoteEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mBackground = getBackground();
+        }
+
+        private void defocusIfNeeded(boolean animate) {
+            if (mRemoteInputView != null || isTemporarilyDetached()) {
+                if (isTemporarilyDetached()) {
+                    // We might get reattached but then the other one of HUN / expanded might steal
+                    // our focus, so we'll need to save our text here.
+                }
+                return;
+            }
+            if (isFocusable() && isEnabled()) {
+                setInnerFocusable(false);
+                if (mRemoteInputView != null) {
+                    mRemoteInputView.onDefocus();
+                }
+                mShowImeOnInputConnection = false;
+            }
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+
+            if (!isShown()) {
+                defocusIfNeeded(false /* animate */);
+            }
+        }
+
+        @Override
+        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+            super.onFocusChanged(focused, direction, previouslyFocusedRect);
+            if (!focused) {
+                defocusIfNeeded(true /* animate */);
+            }
+        }
+
+        @Override
+        public void getFocusedRect(Rect r) {
+            super.getFocusedRect(r);
+            r.top = mScrollY;
+            r.bottom = mScrollY + (mBottom - mTop);
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // Eat the DOWN event here to prevent any default behavior.
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                defocusIfNeeded(true /* animate */);
+                return true;
+            }
+            return super.onKeyUp(keyCode, event);
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+            if (mShowImeOnInputConnection && inputConnection != null) {
+                final InputMethodManager imm = InputMethodManager.getInstance();
+                if (imm != null) {
+                    // onCreateInputConnection is called by InputMethodManager in the middle of
+                    // setting up the connection to the IME; wait with requesting the IME until that
+                    // work has completed.
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            imm.viewClicked(RemoteEditText.this);
+                            imm.showSoftInput(RemoteEditText.this, 0);
+                        }
+                    });
+                }
+            }
+
+            return inputConnection;
+        }
+
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
+        void setInnerFocusable(boolean focusable) {
+            setFocusableInTouchMode(focusable);
+            setFocusable(focusable);
+            setCursorVisible(focusable);
+
+            if (focusable) {
+                requestFocus();
+                setBackground(mBackground);
+            } else {
+                setBackground(null);
+            }
+
+        }
+    }
+}
diff --git a/android/slice/views/ShortcutView.java b/android/slice/views/ShortcutView.java
new file mode 100644
index 0000000..8fe2f1a
--- /dev/null
+++ b/android/slice/views/ShortcutView.java
@@ -0,0 +1,110 @@
+/*
+ * 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.slice.views;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+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;
+
+/**
+ * @hide
+ */
+public class ShortcutView extends SliceModeView {
+
+    private static final String TAG = "ShortcutView";
+
+    private PendingIntent mAction;
+    private Uri mUri;
+    private int mLargeIconSize;
+    private int mSmallIconSize;
+
+    public ShortcutView(Context context) {
+        super(context);
+        mLargeIconSize = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.slice_shortcut_size);
+        mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+        setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize));
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        removeAllViews();
+        SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        SliceItem iconItem = slice.getPrimaryIcon();
+        SliceItem textItem = sliceItem != null
+                ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
+                : SliceQuery.find(slice, SliceItem.TYPE_TEXT);
+        SliceItem colorItem = sliceItem != null
+                ? SliceQuery.find(sliceItem, SliceItem.TYPE_COLOR)
+                : SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        if (colorItem == null) {
+            colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        }
+        // TODO: pick better default colour
+        final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
+        ShapeDrawable circle = new ShapeDrawable(new OvalShape());
+        circle.setTint(color);
+        setBackground(circle);
+        if (iconItem != null) {
+            final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
+            final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
+            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
+                    isLarge, this /* parent */);
+            mAction = sliceItem != null ? sliceItem.getAction()
+                    : null;
+            mUri = slice.getUri();
+            setClickable(true);
+        } else {
+            setClickable(false);
+        }
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_SHORTCUT;
+    }
+
+    @Override
+    public boolean performClick() {
+        if (!callOnClick()) {
+            try {
+                if (mAction != null) {
+                    mAction.send();
+                } else {
+                    Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    getContext().startActivity(intent);
+                }
+            } catch (CanceledException e) {
+                e.printStackTrace();
+            }
+        }
+        return true;
+    }
+}
diff --git a/android/slice/views/SliceView.java b/android/slice/views/SliceView.java
new file mode 100644
index 0000000..f379248
--- /dev/null
+++ b/android/slice/views/SliceView.java
@@ -0,0 +1,249 @@
+/*
+ * 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.slice.views;
+
+import android.annotation.StringDef;
+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;
+
+/**
+ * A view that can display a {@link Slice} in different {@link SliceMode}'s.
+ *
+ * @hide
+ */
+public class SliceView extends LinearLayout {
+
+    private static final String TAG = "SliceView";
+
+    /**
+     * @hide
+     */
+    public abstract static class SliceModeView extends FrameLayout {
+
+        public SliceModeView(Context context) {
+            super(context);
+        }
+
+        /**
+         * @return the {@link SliceMode} of the slice being presented.
+         */
+        public abstract String getMode();
+
+        /**
+         * @param slice the slice to show in this view.
+         */
+        public abstract void setSlice(Slice slice);
+    }
+
+    /**
+     * @hide
+     */
+    @StringDef({
+            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+    })
+    public @interface SliceMode {}
+
+    /**
+     * Mode indicating this slice should be presented in small template format.
+     */
+    public static final String MODE_SMALL       = "SLICE_SMALL";
+    /**
+     * Mode indicating this slice should be presented in large template format.
+     */
+    public static final String MODE_LARGE       = "SLICE_LARGE";
+    /**
+     * Mode indicating this slice should be presented as an icon.
+     */
+    public static final String MODE_SHORTCUT    = "SLICE_ICON";
+
+    /**
+     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+     * that selection.
+     */
+    private static final String MODE_AUTO = "auto";
+
+    private String mMode = MODE_AUTO;
+    private SliceModeView mCurrentView;
+    private final ActionRow mActions;
+    private Slice mCurrentSlice;
+    private boolean mShowActions = true;
+
+    /**
+     * Simple constructor to create a slice view from code.
+     *
+     * @param context The context the view is running in.
+     */
+    public SliceView(Context context) {
+        super(context);
+        setOrientation(LinearLayout.VERTICAL);
+        mActions = new ActionRow(mContext, true);
+        mActions.setBackground(new ColorDrawable(0xffeeeeee));
+        mCurrentView = new LargeTemplateView(mContext);
+        addView(mCurrentView);
+        addView(mActions);
+    }
+
+    /**
+     * @hide
+     */
+    public void bindSlice(Intent intent) {
+        // TODO
+    }
+
+    /**
+     * Binds this view to the {@link Slice} associated with the provided {@link Uri}.
+     */
+    public void bindSlice(Uri sliceUri) {
+        validate(sliceUri);
+        Slice s = mContext.getContentResolver().bindSlice(sliceUri);
+        bindSlice(s);
+    }
+
+    /**
+     * Binds this view to the provided {@link Slice}.
+     */
+    public void bindSlice(Slice slice) {
+        mCurrentSlice = slice;
+        if (mCurrentSlice != null) {
+            reinflate();
+        }
+    }
+
+    /**
+     * Call to clean up the view.
+     */
+    public void unbindSlice() {
+        mCurrentSlice = null;
+    }
+
+    /**
+     * Set the {@link SliceMode} this view should present in.
+     */
+    public void setMode(@SliceMode String mode) {
+        setMode(mode, false /* animate */);
+    }
+
+    /**
+     * @hide
+     */
+    public void setMode(@SliceMode String mode, boolean animate) {
+        if (animate) {
+            Log.e(TAG, "Animation not supported yet");
+        }
+        mMode = mode;
+        reinflate();
+    }
+
+    /**
+     * @return the {@link SliceMode} this view is presenting in.
+     */
+    public @SliceMode String getMode() {
+        if (mMode.equals(MODE_AUTO)) {
+            return MODE_LARGE;
+        }
+        return mMode;
+    }
+
+    /**
+     * @hide
+     *
+     * Whether this view should show a row of actions with it.
+     */
+    public void setShowActionRow(boolean show) {
+        mShowActions = show;
+        reinflate();
+    }
+
+    private SliceModeView createView(String mode) {
+        switch (mode) {
+            case MODE_SHORTCUT:
+                return new ShortcutView(getContext());
+            case MODE_SMALL:
+                return new SmallTemplateView(getContext());
+        }
+        return new LargeTemplateView(getContext());
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        unbindSlice();
+    }
+
+    private void reinflate() {
+        if (mCurrentSlice == null) {
+            return;
+        }
+        // TODO: Smarter mapping here from one state to the next.
+        SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+        SliceItem[] items = mCurrentSlice.getItems();
+        SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+                Slice.HINT_ACTIONS,
+                Slice.HINT_ALT);
+        String mode = getMode();
+        if (!mode.equals(mCurrentView.getMode())) {
+            removeAllViews();
+            mCurrentView = createView(mode);
+            addView(mCurrentView);
+            addView(mActions);
+        }
+        if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) {
+            mCurrentView.setVisibility(View.VISIBLE);
+            mCurrentView.setSlice(mCurrentSlice);
+        } else {
+            mCurrentView.setVisibility(View.GONE);
+        }
+
+        boolean showActions = mShowActions && actionRow != null
+                && !mode.equals(MODE_SHORTCUT);
+        if (showActions) {
+            mActions.setActions(actionRow, color);
+            mActions.setVisibility(View.VISIBLE);
+        } else {
+            mActions.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // TODO -- may need to rethink for AGSA
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            requestDisallowInterceptTouchEvent(true);
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    private static void validate(Uri sliceUri) {
+        if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+        if (sliceUri.getPathSegments().size() == 0) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+    }
+}
diff --git a/android/slice/views/SliceViewUtil.java b/android/slice/views/SliceViewUtil.java
new file mode 100644
index 0000000..1b5a6d1
--- /dev/null
+++ b/android/slice/views/SliceViewUtil.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.slice.views;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+public class SliceViewUtil {
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorAccent(Context context) {
+        return getColorAttr(context, android.R.attr.colorAccent);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorError(Context context) {
+        return getColorAttr(context, android.R.attr.colorError);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getDefaultColor(Context context, int resId) {
+        final ColorStateList list = context.getResources().getColorStateList(resId,
+                context.getTheme());
+
+        return list.getDefaultColor();
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getDisabled(Context context, int inputColor) {
+        return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int applyAlphaAttr(Context context, int attr, int inputColor) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        float alpha = ta.getFloat(0, 0);
+        ta.recycle();
+        return applyAlpha(alpha, inputColor);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int applyAlpha(float alpha, int inputColor) {
+        alpha *= Color.alpha(inputColor);
+        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+                Color.blue(inputColor));
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorAttr(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        @ColorInt
+        int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    /**
+     * @hide
+     */
+    public static int getThemeAttr(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        int theme = ta.getResourceId(0, 0);
+        ta.recycle();
+        return theme;
+    }
+
+    /**
+     * @hide
+     */
+    public static Drawable getDrawable(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        Drawable drawable = ta.getDrawable(0);
+        ta.recycle();
+        return drawable;
+    }
+
+    /**
+     * @hide
+     */
+    public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
+            boolean isLarge, ViewGroup parent) {
+        ImageView v = new ImageView(context);
+        v.setImageIcon(icon);
+        parent.addView(v);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        if (isLarge) {
+            // XXX better way to convert from icon -> bitmap or crop an icon (?)
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            v.layout(0, 0, iconSize, iconSize);
+            v.draw(iconCanvas);
+            v.setImageBitmap(getCircularBitmap(iconBm));
+        } else {
+            v.setColorFilter(Color.WHITE);
+        }
+        lp.width = iconSize;
+        lp.height = iconSize;
+        lp.gravity = Gravity.CENTER;
+    }
+
+    /**
+     * @hide
+     */
+    public static Bitmap getCircularBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+                bitmap.getHeight(), Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+        final Paint paint = new Paint();
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
+                bitmap.getWidth() / 2, paint);
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+        return output;
+    }
+}
diff --git a/android/slice/views/SmallTemplateView.java b/android/slice/views/SmallTemplateView.java
new file mode 100644
index 0000000..b0b181e
--- /dev/null
+++ b/android/slice/views/SmallTemplateView.java
@@ -0,0 +1,211 @@
+/*
+ * 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.slice.views;
+
+import android.app.PendingIntent.CanceledException;
+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;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Small template is also used to construct list items for use with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+public class SmallTemplateView extends SliceModeView implements SliceListView {
+
+    private static final String TAG = "SmallTemplateView";
+
+    private int mIconSize;
+    private int mPadding;
+
+    private LinearLayout mStartContainer;
+    private TextView mTitleText;
+    private TextView mSecondaryText;
+    private LinearLayout mEndContainer;
+
+    public SmallTemplateView(Context context) {
+        super(context);
+        inflate(context, R.layout.slice_small_template, this);
+        mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+        mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
+
+        mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
+        mTitleText = (TextView) findViewById(android.R.id.title);
+        mSecondaryText = (TextView) findViewById(android.R.id.summary);
+        mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_SMALL;
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        resetViews();
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        int color = colorItem != null ? colorItem.getColor() : -1;
+
+        // Look for any title elements
+        List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
+                null);
+        boolean hasTitleText = false;
+        boolean hasTitleItem = false;
+        for (int i = 0; i < titleItems.size(); i++) {
+            SliceItem item = titleItems.get(i);
+            if (!hasTitleItem) {
+                // icon, action icon, or timestamp
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    hasTitleItem = addIcon(item, color, mStartContainer);
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mStartContainer);
+                    hasTitleItem = true;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    hasTitleItem = true;
+                }
+            }
+            if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
+                mTitleText.setText(item.getText());
+                hasTitleText = true;
+            }
+            if (hasTitleText && hasTitleItem) {
+                break;
+            }
+        }
+        mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
+        mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
+
+        if (slice.getType() != SliceItem.TYPE_SLICE) {
+            return;
+        }
+
+        // Deal with remaining items
+        int itemCount = 0;
+        boolean hasSummary = false;
+        ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
+                Arrays.asList(slice.getSlice().getItems()));
+        for (int i = 0; i < sliceItems.size(); i++) {
+            SliceItem item = sliceItems.get(i);
+            if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
+                    && !item.hasHint(Slice.HINT_TITLE)) {
+                // TODO -- Should combine all text items?
+                mSecondaryText.setText(item.getText());
+                hasSummary = true;
+            }
+            if (itemCount <= 3) {
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    if (addIcon(item, color, mEndContainer)) {
+                        itemCount++;
+                    }
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mEndContainer);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    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]);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints()));
+    }
+
+    /**
+     * @return Whether an icon was added.
+     */
+    private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
+        SliceItem image = null;
+        SliceItem action = null;
+        if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
+            image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
+            action = sliceItem;
+        } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
+            image = sliceItem;
+        }
+        if (image != null) {
+            ImageView iv = new ImageView(getContext());
+            iv.setImageIcon(image.getIcon());
+            if (action != null) {
+                final SliceItem sliceAction = action;
+                iv.setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                sliceAction.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+                iv.setBackground(SliceViewUtil.getDrawable(getContext(),
+                        android.R.attr.selectableItemBackground));
+            }
+            if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
+                iv.setColorFilter(color);
+            }
+            container.addView(iv);
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            lp.setMarginStart(mPadding);
+            return true;
+        }
+        return false;
+    }
+
+    private String convertTimeToString(long time) {
+        // TODO -- figure out what format(s) we support
+        Date date = new Date(time);
+        Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
+        return format.format(date);
+    }
+
+    private void resetViews() {
+        mStartContainer.removeAllViews();
+        mEndContainer.removeAllViews();
+        mTitleText.setText(null);
+        mSecondaryText.setText(null);
+    }
+}
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index f8e6e81..a046d95 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-alpha9-1");
+    private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-beta2");
 
     /**
      * Version code for Room
@@ -43,12 +43,12 @@
     /**
      * Version code for RecyclerView & Room paging
      */
-    public static final Version PAGING = new Version("1.0.0-alpha1");
+    public static final Version PAGING = new Version("1.0.0-alpha3");
 
     /**
      * Version code for Lifecycle libs that are required by the support library
      */
-    public static final Version LIFECYCLES_CORE = new Version("1.0.0");
+    public static final Version LIFECYCLES_CORE = new Version("1.0.2");
 
     /**
      * Version code for Lifecycle runtime libs that are required by the support library
diff --git a/android/support/Version.java b/android/support/Version.java
index b88d8cf..69b7f5e 100644
--- a/android/support/Version.java
+++ b/android/support/Version.java
@@ -42,7 +42,29 @@
 
     @Override
     public int compareTo(Version version) {
-        return mMajor != version.mMajor ? mMajor - version.mMajor : mMinor - version.mMinor;
+        if (mMajor != version.mMajor) {
+            return mMajor - version.mMajor;
+        }
+        if (mMinor != version.mMinor) {
+            return mMinor - version.mMinor;
+        }
+        if (mPatch != version.mPatch) {
+            return mPatch - version.mPatch;
+        }
+        if (mExtra == null) {
+            if (version.mExtra == null) {
+                return 0;
+            }
+            // not having any extra is always a later version
+            return 1;
+        } else {
+            if (version.mExtra == null) {
+                // not having any extra is always a later version
+                return -1;
+            }
+            // gradle uses lexicographic ordering
+            return mExtra.compareTo(version.mExtra);
+        }
     }
 
     public boolean isPatch() {
@@ -73,4 +95,26 @@
     public String toString() {
         return mMajor + "." + mMinor + "." + mPatch + (mExtra != null ? mExtra : "");
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Version version = (Version) o;
+
+        if (mMajor != version.mMajor) return false;
+        if (mMinor != version.mMinor) return false;
+        if (mPatch != version.mPatch) return false;
+        return mExtra != null ? mExtra.equals(version.mExtra) : version.mExtra == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mMajor;
+        result = 31 * result + mMinor;
+        result = 31 * result + mPatch;
+        result = 31 * result + (mExtra != null ? mExtra.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/android/support/VersionFileWriterTask.java b/android/support/VersionFileWriterTask.java
new file mode 100644
index 0000000..aafa023
--- /dev/null
+++ b/android/support/VersionFileWriterTask.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import com.android.build.gradle.LibraryExtension;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Task that allows to write a version to a given output file.
+ */
+public class VersionFileWriterTask extends DefaultTask {
+    public static final String RESOURCE_DIRECTORY = "generatedResources";
+    public static final String VERSION_FILE_PATH =
+            RESOURCE_DIRECTORY + "/META-INF/%s_%s.version";
+
+    private String mVersion;
+    private File mOutputFile;
+
+    /**
+     * Sets up Android Library project to have a task that generates a version file.
+     *
+     * @param project an Android Library project.
+     */
+    public static void setUpAndroidLibrary(Project project) {
+        project.afterEvaluate(new Action<Project>() {
+            @Override
+            public void execute(Project project) {
+                LibraryExtension library =
+                        project.getExtensions().findByType(LibraryExtension.class);
+
+                String group = (String) project.getProperties().get("group");
+                String artifactId = (String) project.getProperties().get("name");
+                String version = (String) project.getProperties().get("version");
+
+                // Add a java resource file to the library jar for version tracking purposes.
+                File artifactName = new File(project.getBuildDir(),
+                        String.format(VersionFileWriterTask.VERSION_FILE_PATH,
+                                group, artifactId));
+
+                VersionFileWriterTask writeVersionFile =
+                        project.getTasks().create("writeVersionFile", VersionFileWriterTask.class);
+                writeVersionFile.setVersion(version);
+                writeVersionFile.setOutputFile(artifactName);
+
+                library.getLibraryVariants().all(
+                        libraryVariant -> libraryVariant.getProcessJavaResources().dependsOn(
+                                writeVersionFile));
+
+                library.getSourceSets().getByName("main").getResources().srcDir(
+                        new File(project.getBuildDir(), VersionFileWriterTask.RESOURCE_DIRECTORY)
+                );
+            }
+        });
+    }
+
+    @Input
+    public String getVersion() {
+        return mVersion;
+    }
+
+    public void setVersion(String version) {
+        mVersion = version;
+    }
+
+    @OutputFile
+    public File getOutputFile() {
+        return mOutputFile;
+    }
+
+    public void setOutputFile(File outputFile) {
+        mOutputFile = outputFile;
+    }
+
+    /**
+     * The main method for actually writing out the file.
+     *
+     * @throws IOException
+     */
+    @TaskAction
+    public void run() throws IOException {
+        PrintWriter writer = new PrintWriter(mOutputFile);
+        writer.println(mVersion);
+        writer.close();
+    }
+}
diff --git a/android/support/annotation/NavigationRes.java b/android/support/annotation/NavigationRes.java
new file mode 100644
index 0000000..a051026
--- /dev/null
+++ b/android/support/annotation/NavigationRes.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 android.support.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a navigation resource reference (e.g. {@code R.navigation.flow}).
+ */
+@Documented
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface NavigationRes {
+}
diff --git a/android/support/car/utils/ColumnCalculator.java b/android/support/car/utils/ColumnCalculator.java
new file mode 100644
index 0000000..96e081b
--- /dev/null
+++ b/android/support/car/utils/ColumnCalculator.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.support.car.utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.car.R;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Utility class that calculates the size of the columns that will fit on the screen. A column's
+ * width is determined by the size of the margins and gutters (space between the columns) that fit
+ * on-screen.
+ *
+ * <p>Refer to the appropriate dimens and integers for the size of the margins and number of
+ * columns.
+ */
+public class ColumnCalculator {
+    private static final String TAG = "ColumnCalculator";
+
+    private static ColumnCalculator sInstance;
+    private static int sScreenWidth;
+
+    private int mNumOfColumns;
+    private int mNumOfGutters;
+    private int mColumnWidth;
+    private int mGutterSize;
+
+    /**
+     * Gets an instance of the {@link ColumnCalculator}. If this is the first time that this
+     * method has been called, then the given {@link Context} will be used to retrieve resources.
+     *
+     * @param context The current calling Context.
+     * @return An instance of {@link ColumnCalculator}.
+     */
+    public static ColumnCalculator getInstance(Context context) {
+        if (sInstance == null) {
+            WindowManager windowManager = (WindowManager) context.getSystemService(
+                    Context.WINDOW_SERVICE);
+            DisplayMetrics displayMetrics = new DisplayMetrics();
+            windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+            sScreenWidth = displayMetrics.widthPixels;
+
+            sInstance = new ColumnCalculator(context);
+        }
+
+        return sInstance;
+    }
+
+    private ColumnCalculator(Context context) {
+        Resources res = context.getResources();
+        int marginSize = res.getDimensionPixelSize(R.dimen.car_screen_margin_size);
+        mGutterSize = res.getDimensionPixelSize(R.dimen.car_screen_gutter_size);
+        mNumOfColumns = res.getInteger(R.integer.car_screen_num_of_columns);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, String.format("marginSize: %d; numOfColumns: %d; gutterSize: %d",
+                    marginSize, mNumOfColumns, mGutterSize));
+        }
+
+        // The gutters appear between each column. As a result, the number of gutters is one less
+        // than the number of columns.
+        mNumOfGutters = mNumOfColumns - 1;
+
+        // Determine the spacing that is allowed to be filled by the columns by subtracting margins
+        // on both size of the screen and the space taken up by the gutters.
+        int spaceForColumns = sScreenWidth - (2 * marginSize) - (mNumOfGutters * mGutterSize);
+
+        mColumnWidth = spaceForColumns / mNumOfColumns;
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "mColumnWidth: " + mColumnWidth);
+        }
+    }
+
+    /**
+     * Returns the total number of columns that fit on the current screen.
+     *
+     * @return The total number of columns that fit on the screen.
+     */
+    public int getNumOfColumns() {
+        return mNumOfColumns;
+    }
+
+    /**
+     * Returns the size in pixels of each column. The column width is determined by the size of the
+     * screen divided by the number of columns, size of gutters and margins.
+     *
+     * @return The width of a single column in pixels.
+     */
+    public int getColumnWidth() {
+        return mColumnWidth;
+    }
+
+    /**
+     * Returns the total number of gutters that fit on screen. A gutter is the space between each
+     * column. This value is always one less than the number of columns.
+     *
+     * @return The number of gutters on screen.
+     */
+    public int getNumOfGutters() {
+        return mNumOfGutters;
+    }
+
+    /**
+     * Returns the size of each gutter in pixels. A gutter is the space between each column.
+     *
+     * @return The size of a single gutter in pixels.
+     */
+    public int getGutterSize() {
+        return mGutterSize;
+    }
+
+    /**
+     * Returns the size in pixels for the given number of columns. This value takes into account
+     * the size of the gutter between the columns as well. For example, for a column span of four,
+     * the size returned is the sum of four columns and three gutters.
+     *
+     * @return The size in pixels for a given column span.
+     */
+    public int getSizeForColumnSpan(int columnSpan) {
+        int gutterSpan = columnSpan - 1;
+        return columnSpan * mColumnWidth + gutterSpan * mGutterSize;
+    }
+}
diff --git a/android/support/car/widget/CarItemAnimator.java b/android/support/car/widget/CarItemAnimator.java
new file mode 100644
index 0000000..4dd3212
--- /dev/null
+++ b/android/support/car/widget/CarItemAnimator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.widget;
+
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+
+/** {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior. */
+public class CarItemAnimator extends DefaultItemAnimator {
+
+    private final CarLayoutManager mLayoutManager;
+
+    public CarItemAnimator(CarLayoutManager layoutManager) {
+        mLayoutManager = layoutManager;
+    }
+
+    @Override
+    public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+            RecyclerView.ViewHolder newHolder,
+            int fromX,
+            int fromY,
+            int toX,
+            int toY) {
+        // The default behavior will cross fade the old view and the new one. However, if we
+        // have a card on a colored background, it will make it appear as if a changing card
+        // fades in and out.
+        float alpha = 0f;
+        if (newHolder != null) {
+            alpha = newHolder.itemView.getAlpha();
+        }
+        boolean ret = super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
+        if (newHolder != null) {
+            newHolder.itemView.setAlpha(alpha);
+        }
+        return ret;
+    }
+
+    @Override
+    public void onMoveFinished(RecyclerView.ViewHolder item) {
+        // The item animator uses translation heavily internally. However, we also use translation
+        // to create the paging affect. When an item's move is animated, it will mess up the
+        // translation we have set on it so we must re-offset the rows once the animations finish.
+
+        // isRunning(ItemAnimationFinishedListener) is the awkward API used to determine when all
+        // animations have finished.
+        isRunning(mFinishedListener);
+    }
+
+    private final ItemAnimatorFinishedListener mFinishedListener =
+            new ItemAnimatorFinishedListener() {
+                @Override
+                public void onAnimationsFinished() {
+                    mLayoutManager.offsetRows();
+                }
+            };
+}
diff --git a/android/support/car/widget/CarLayoutManager.java b/android/support/car/widget/CarLayoutManager.java
new file mode 100644
index 0000000..d0d3a9e
--- /dev/null
+++ b/android/support/car/widget/CarLayoutManager.java
@@ -0,0 +1,1636 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.support.car.R;
+import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Recycler;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that
+ * it has a few tricks up its sleeve.
+ *
+ * <ol>
+ *   <li>In a normal ListView, when views reach the top of the list, they are clipped. In
+ *       CarLayoutManager, views have the option of flying off of the top of the screen as the next
+ *       row settles in to place. This functionality can be enabled or disabled with {@link
+ *       #setOffsetRows(boolean)}.
+ *   <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle on the
+ *       next page.
+ *   <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that the
+ *       last page can be properly aligned.
+ * </ol>
+ *
+ * This LayoutManger should be used with {@link CarRecyclerView}.
+ */
+public class CarLayoutManager extends RecyclerView.LayoutManager {
+    private static final String TAG = "CarLayoutManager";
+
+    /**
+     * Any fling below the threshold will just scroll to the top fully visible row. The units is
+     * whatever {@link android.widget.Scroller} would return.
+     *
+     * <p>A reasonable value is ~200
+     *
+     * <p>This can be disabled by setting the threshold to -1.
+     */
+    private static final int FLING_THRESHOLD_TO_PAGINATE = -1;
+
+    /**
+     * Any fling shorter than this threshold (in px) will just scroll to the top fully visible row.
+     *
+     * <p>A reasonable value is 15.
+     *
+     * <p>This can be disabled by setting the distance to -1.
+     */
+    private static final int DRAG_DISTANCE_TO_PAGINATE = -1;
+
+    /**
+     * If you scroll really quickly, you can hit the end of the laid out rows before Android has a
+     * chance to layout more. To help counter this, we can layout a number of extra rows past
+     * wherever the focus is if necessary.
+     */
+    private static final int NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS = 2;
+
+    /**
+     * Scroll bar calculation is a bit complicated. This basically defines the granularity we want
+     * our scroll bar to move. Set this to 1 means our scrollbar will have really jerky movement.
+     * Setting it too big will risk an overflow (although there is no performance impact). Ideally
+     * we want to set this higher than the height of our list view. We can't use our list view
+     * height directly though because we might run into situations where getHeight() returns 0,
+     * for example, when the view is not yet measured.
+     */
+    private static final int SCROLL_RANGE = 1000;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({BEFORE, AFTER})
+    private @interface LayoutDirection {}
+
+    private static final int BEFORE = 0;
+    private static final int AFTER = 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ROW_OFFSET_MODE_INDIVIDUAL, ROW_OFFSET_MODE_PAGE})
+    public @interface RowOffsetMode {}
+
+    public static final int ROW_OFFSET_MODE_INDIVIDUAL = 0;
+    public static final int ROW_OFFSET_MODE_PAGE = 1;
+
+    private final AccelerateInterpolator mDanglingRowInterpolator = new AccelerateInterpolator(2);
+    private final Context mContext;
+
+    /** Determines whether or not rows will be offset as they slide off screen * */
+    private boolean mOffsetRows;
+
+    /** Determines whether rows will be offset individually or a page at a time * */
+    @RowOffsetMode private int mRowOffsetMode = ROW_OFFSET_MODE_PAGE;
+
+    /**
+     * The LayoutManager only gets {@link #onScrollStateChanged(int)} updates. This enables the
+     * scroll state to be used anywhere.
+     */
+    private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
+
+    /** Used to inspect the current scroll state to help with the various calculations. */
+    private CarSmoothScroller mSmoothScroller;
+
+    private PagedListView.OnScrollListener mOnScrollListener;
+
+    /** The distance that the list has actually scrolled in the most recent drag gesture. */
+    private int mLastDragDistance = 0;
+
+    /** {@code True} if the current drag was limited/capped because it was at some boundary. */
+    private boolean mReachedLimitOfDrag;
+
+    /** The index of the first item on the current page. */
+    private int mAnchorPageBreakPosition = 0;
+
+    /** The index of the first item on the previous page. */
+    private int mUpperPageBreakPosition = -1;
+
+    /** The index of the first item on the next page. */
+    private int mLowerPageBreakPosition = -1;
+
+    /** Used in the bookkeeping of mario style scrolling to prevent extra calculations. */
+    private int mLastChildPositionToRequestFocus = -1;
+
+    private int mSampleViewHeight = -1;
+
+    /** Used for onPageUp and onPageDown */
+    private int mViewsPerPage = 1;
+
+    private int mCurrentPage = 0;
+
+    private static final int MAX_ANIMATIONS_IN_CACHE = 30;
+    /**
+     * Cache of TranslateAnimation per child view. These are needed since using a single animation
+     * for all children doesn't apply the animation effect multiple times. Key = the view the
+     * animation will transform.
+     */
+    private LruCache<View, TranslateAnimation> mFlyOffscreenAnimations;
+
+    /** Set the anchor to the following position on the next layout pass. */
+    private int mPendingScrollPosition = -1;
+
+    public CarLayoutManager(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new RecyclerView.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        return true;
+    }
+
+    /**
+     * onLayoutChildren is sort of like a "reset" for the layout state. At a high level, it should:
+     *
+     * <ol>
+     *   <li>Check the current views to get the current state of affairs
+     *   <li>Detach all views from the window (a lightweight operation) so that rows not re-added
+     *       will be removed after onLayoutChildren.
+     *   <li>Re-add rows as necessary.
+     * </ol>
+     *
+     * @see super#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)
+     */
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        /*
+         * The anchor view is the first fully visible view on screen at the beginning of
+         * onLayoutChildren (or 0 if there is none). This row will be laid out first. After that,
+         * layoutNextRow will layout rows above and below it until the boundaries of what should be
+         * laid out have been reached. See shouldLayoutNextRow(View, int) for more info.
+         */
+        int anchorPosition = 0;
+        int anchorTop = -1;
+        if (mPendingScrollPosition == -1) {
+            View anchor = getFirstFullyVisibleChild();
+            if (anchor != null) {
+                anchorPosition = getPosition(anchor);
+                anchorTop = getDecoratedTop(anchor);
+            }
+        } else {
+            anchorPosition = mPendingScrollPosition;
+            mPendingScrollPosition = -1;
+            mAnchorPageBreakPosition = anchorPosition;
+            mUpperPageBreakPosition = -1;
+            mLowerPageBreakPosition = -1;
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(
+                    TAG,
+                    String.format(
+                            ":: onLayoutChildren anchorPosition:%s, anchorTop:%s,"
+                                    + " mPendingScrollPosition: %s, mAnchorPageBreakPosition:%s,"
+                                    + " mUpperPageBreakPosition:%s, mLowerPageBreakPosition:%s",
+                            anchorPosition,
+                            anchorTop,
+                            mPendingScrollPosition,
+                            mAnchorPageBreakPosition,
+                            mUpperPageBreakPosition,
+                            mLowerPageBreakPosition));
+        }
+
+        /*
+         * Detach all attached view for 2 reasons:
+         *
+         * 1) So that views are put in the scrap heap. This enables us to call {@link
+         *    RecyclerView.Recycler#getViewForPosition(int)} which will either return one of these
+         *    detached views if it is in the scrap heap, one from the recycled pool (will only call
+         *    onBind in the adapter), or create an entirely new row if needed (will call onCreate
+         *    and onBind in the adapter).
+         * 2) So that views are automatically removed if they are not manually re-added.
+         */
+        detachAndScrapAttachedViews(recycler);
+
+        /*
+         * Layout the views recursively.
+         *
+         * It's possible that this re-layout is triggered because an item gets removed. If the
+         * anchor view is at the end of the list, the anchor view position will be bigger than the
+         * number of available items. Correct that, and only start the layout if the anchor
+         * position is valid.
+         */
+        anchorPosition = Math.min(anchorPosition, getItemCount() - 1);
+        if (anchorPosition >= 0) {
+            View anchor = layoutAnchor(recycler, anchorPosition, anchorTop);
+            View adjacentRow = anchor;
+            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+            }
+            adjacentRow = anchor;
+            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+            }
+        }
+
+        updatePageBreakPositions();
+        offsetRows();
+
+        if (Log.isLoggable(TAG, Log.VERBOSE) && getChildCount() > 1) {
+            Log.v(TAG, "Currently showing "
+                    + getChildCount()
+                    + " views "
+                    + getPosition(getChildAt(0))
+                    + " to "
+                    + getPosition(getChildAt(getChildCount() - 1))
+                    + " anchor "
+                    + anchorPosition);
+        }
+        // Should be at least 1
+        mViewsPerPage =
+                Math.max(getLastFullyVisibleChildIndex() + 1 - getFirstFullyVisibleChildIndex(), 1);
+        mCurrentPage = getFirstFullyVisibleChildPosition() / mViewsPerPage;
+        Log.v(TAG, "viewsPerPage " + mViewsPerPage);
+    }
+
+    /**
+     * scrollVerticallyBy does the work of what should happen when the list scrolls in addition to
+     * handling cases where the list hits the end. It should be lighter weight than
+     * onLayoutChildren. It doesn't have to detach all views. It only looks at the end of the list
+     * and removes views that have gone out of bounds and lays out new ones that scroll in.
+     *
+     * @param dy The amount that the list is supposed to scroll. > 0 means the list is scrolling
+     *     down. < 0 means the list is scrolling up.
+     * @param recycler The recycler that enables views to be reused or created as they scroll in.
+     * @param state Various information about the current state of affairs.
+     * @return The amount the list actually scrolled.
+     * @see super#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)
+     */
+    @Override
+    public int scrollVerticallyBy(
+            int dy, @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) {
+        // If the list is empty, we can prevent the overscroll glow from showing by just
+        // telling RecycerView that we scrolled.
+        if (getItemCount() == 0) {
+            return dy;
+        }
+
+        // Prevent redundant computations if there is definitely nowhere to scroll to.
+        if (getChildCount() <= 1 || dy == 0) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+
+        View firstChild = getChildAt(0);
+        if (firstChild == null) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+        int firstChildPosition = getPosition(firstChild);
+        RecyclerView.LayoutParams firstChildParams = getParams(firstChild);
+        int firstChildTopWithMargin = getDecoratedTop(firstChild) - firstChildParams.topMargin;
+
+        View lastFullyVisibleView = getChildAt(getLastFullyVisibleChildIndex());
+        if (lastFullyVisibleView == null) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+        boolean isLastViewVisible = getPosition(lastFullyVisibleView) == getItemCount() - 1;
+
+        View firstFullyVisibleChild = getFirstFullyVisibleChild();
+        if (firstFullyVisibleChild == null) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+        int firstFullyVisiblePosition = getPosition(firstFullyVisibleChild);
+        RecyclerView.LayoutParams firstFullyVisibleChildParams = getParams(firstFullyVisibleChild);
+        int topRemainingSpace =
+                getDecoratedTop(firstFullyVisibleChild)
+                        - firstFullyVisibleChildParams.topMargin
+                        - getPaddingTop();
+
+        if (isLastViewVisible
+                && firstFullyVisiblePosition == mAnchorPageBreakPosition
+                && dy > topRemainingSpace
+                && dy > 0) {
+            // Prevent dragging down more than 1 page. As a side effect, this also prevents you
+            // from dragging past the bottom because if you are on the second to last page, it
+            // prevents you from dragging past the last page.
+            dy = topRemainingSpace;
+            mReachedLimitOfDrag = true;
+        } else if (dy < 0
+                && firstChildPosition == 0
+                && firstChildTopWithMargin + Math.abs(dy) > getPaddingTop()) {
+            // Prevent scrolling past the beginning
+            dy = firstChildTopWithMargin - getPaddingTop();
+            mReachedLimitOfDrag = true;
+        } else {
+            mReachedLimitOfDrag = false;
+        }
+
+        boolean isDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING;
+        if (isDragging) {
+            mLastDragDistance += dy;
+        }
+        // We offset by -dy because the views translate in the opposite direction that the
+        // list scrolls (think about it.)
+        offsetChildrenVertical(-dy);
+
+        // The last item in the layout should never scroll above the viewport
+        View view = getChildAt(getChildCount() - 1);
+        if (view.getTop() < 0) {
+            view.setTop(0);
+        }
+
+        // This is the meat of this function. We remove views on the trailing edge of the scroll
+        // and add views at the leading edge as necessary.
+        View adjacentRow;
+        if (dy > 0) {
+            recycleChildrenFromStart(recycler);
+            adjacentRow = getChildAt(getChildCount() - 1);
+            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+            }
+        } else {
+            recycleChildrenFromEnd(recycler);
+            adjacentRow = getChildAt(0);
+            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+            }
+        }
+        // Now that the correct views are laid out, offset rows as necessary so we can do whatever
+        // fancy animation we want such as having the top view fly off the screen as the next one
+        // settles in to place.
+        updatePageBreakPositions();
+        offsetRows();
+
+        if (getChildCount() > 1) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(
+                        TAG,
+                        String.format(
+                                "Currently showing  %d views (%d to %d)",
+                                getChildCount(),
+                                getPosition(getChildAt(0)),
+                                getPosition(getChildAt(getChildCount() - 1))));
+            }
+        }
+        updatePagedState();
+        return dy;
+    }
+
+    private void updatePagedState() {
+        int page = getFirstFullyVisibleChildPosition() / mViewsPerPage;
+        if (mOnScrollListener != null) {
+            if (page > mCurrentPage) {
+                mOnScrollListener.onPageDown();
+            } else if (page < mCurrentPage) {
+                mOnScrollListener.onPageUp();
+            }
+        }
+        mCurrentPage = page;
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        mPendingScrollPosition = position;
+        requestLayout();
+    }
+
+    @Override
+    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+            int position) {
+        /*
+         * startSmoothScroll will handle stopping the old one if there is one. We only keep a copy
+         * of it to handle the translation of rows as they slide off the screen in
+         * offsetRowsWithPageBreak().
+         */
+        mSmoothScroller = new CarSmoothScroller(mContext, position);
+        mSmoothScroller.setTargetPosition(position);
+        startSmoothScroll(mSmoothScroller);
+    }
+
+    /** Miscellaneous bookkeeping. */
+    @Override
+    public void onScrollStateChanged(int state) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, ":: onScrollStateChanged " + state);
+        }
+        if (state == RecyclerView.SCROLL_STATE_IDLE) {
+            // If the focused view is off screen, give focus to one that is.
+            // If the first fully visible view is first in the list, focus the first item.
+            // Otherwise, focus the second so that you have the first item as scrolling context.
+            View focusedChild = getFocusedChild();
+            if (focusedChild != null
+                    && (getDecoratedTop(focusedChild) >= getHeight() - getPaddingBottom()
+                    || getDecoratedBottom(focusedChild) <= getPaddingTop())) {
+                focusedChild.clearFocus();
+                requestLayout();
+            }
+
+        } else if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
+            mLastDragDistance = 0;
+        }
+
+        if (state != RecyclerView.SCROLL_STATE_SETTLING) {
+            mSmoothScroller = null;
+        }
+
+        mScrollState = state;
+        updatePageBreakPositions();
+    }
+
+    @Override
+    public void onItemsChanged(RecyclerView recyclerView) {
+        super.onItemsChanged(recyclerView);
+        // When item changed, our sample view height is no longer accurate, and need to be
+        // recomputed.
+        mSampleViewHeight = -1;
+    }
+
+    /**
+     * Gives us the opportunity to override the order of the focused views. By default, it will just
+     * go from top to bottom. However, if there is no focused views, we take over the logic and
+     * start the focused views from the middle of what is visible and move from there until the
+     * end of the laid out views in the specified direction.
+     */
+    @Override
+    public boolean onAddFocusables(
+            RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) {
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            // If there is a view that already has focus, we can just return false and the normal
+            // Android addFocusables will work fine.
+            return false;
+        }
+
+        // Now we know that there isn't a focused view. We need to set up focusables such that
+        // instead of just focusing the first item that has been laid out, it focuses starting
+        // from a visible item.
+
+        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+        if (firstFullyVisibleChildIndex == -1) {
+            // Somehow there is a focused view but there is no fully visible view. There shouldn't
+            // be a way for this to happen but we'd better stop here and return instead of
+            // continuing on with -1.
+            Log.w(TAG, "There is a focused child but no first fully visible child.");
+            return false;
+        }
+        View firstFullyVisibleChild = getChildAt(firstFullyVisibleChildIndex);
+        int firstFullyVisibleChildPosition = getPosition(firstFullyVisibleChild);
+
+        int firstFocusableChildIndex = firstFullyVisibleChildIndex;
+        if (firstFullyVisibleChildPosition > 0 && firstFocusableChildIndex + 1 < getItemCount()) {
+            // We are somewhere in the middle of the list. Instead of starting focus on the first
+            // item, start focus on the second item to give some context that we aren't at
+            // the beginning.
+            firstFocusableChildIndex++;
+        }
+
+        if (direction == View.FOCUS_FORWARD) {
+            // Iterate from the first focusable view to the end.
+            for (int i = firstFocusableChildIndex; i < getChildCount(); i++) {
+                views.add(getChildAt(i));
+            }
+            return true;
+        } else if (direction == View.FOCUS_BACKWARD) {
+            // Iterate from the first focusable view to the beginning.
+            for (int i = firstFocusableChildIndex; i >= 0; i--) {
+                views.add(getChildAt(i));
+            }
+            return true;
+        } else if (direction == View.FOCUS_DOWN) {
+            // Framework calls onAddFocusables with FOCUS_DOWN direction when the focus is first
+            // gained. Thereafter, it calls onAddFocusables with FOCUS_FORWARD or FOCUS_BACKWARD.
+            // First we try to put the focus back on the last focused item, if it is visible
+            int lastFocusedVisibleChildIndex = getLastFocusedChildIndexIfVisible();
+            if (lastFocusedVisibleChildIndex != -1) {
+                views.add(getChildAt(lastFocusedVisibleChildIndex));
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public View onFocusSearchFailed(
+            View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state) {
+        // This doesn't seem to get called the way focus is handled in gearhead...
+        return null;
+    }
+
+    /**
+     * This is the function that decides where to scroll to when a new view is focused. You can get
+     * the position of the currently focused child through the child parameter. Once you have that,
+     * determine where to smooth scroll to and scroll there.
+     *
+     * @param parent The RecyclerView hosting this LayoutManager
+     * @param state Current state of RecyclerView
+     * @param child Direct child of the RecyclerView containing the newly focused view
+     * @param focused The newly focused view. This may be the same view as child or it may be null
+     * @return {@code true} if the default scroll behavior should be suppressed
+     */
+    @Override
+    public boolean onRequestChildFocus(
+            RecyclerView parent, RecyclerView.State state, View child, View focused) {
+        if (child == null) {
+            Log.w(TAG, "onRequestChildFocus with a null child!");
+            return true;
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: onRequestChildFocus child: %s, focused: %s", child,
+                    focused));
+        }
+
+        return onRequestChildFocusMarioStyle(parent, child);
+    }
+
+    /**
+     * Goal: the scrollbar maintains the same size throughout scrolling and that the scrollbar
+     * reaches the bottom of the screen when the last item is fully visible. This is because there
+     * are multiple points that could be considered the bottom since the last item can scroll past
+     * the bottom edge of the screen.
+     *
+     * <p>To find the extent, we divide the number of items that can fit on screen by the number of
+     * items in total.
+     */
+    @Override
+    public int computeVerticalScrollExtent(RecyclerView.State state) {
+        if (getChildCount() <= 1) {
+            return 0;
+        }
+
+        int sampleViewHeight = getSampleViewHeight();
+        int availableHeight = getAvailableHeight();
+        int sampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+
+        if (state.getItemCount() <= sampleViewsThatCanFitOnScreen) {
+            return SCROLL_RANGE;
+        } else {
+            return SCROLL_RANGE * sampleViewsThatCanFitOnScreen / state.getItemCount();
+        }
+    }
+
+    /**
+     * The scrolling offset is calculated by determining what position is at the top of the list.
+     * However, instead of using fixed integer positions for each row, the scroll position is
+     * factored in and the position is recalculated as a float that takes in to account the
+     * current scroll state. This results in a smooth animation for the scrollbar when the user
+     * scrolls the list.
+     */
+    @Override
+    public int computeVerticalScrollOffset(RecyclerView.State state) {
+        View firstChild = getFirstFullyVisibleChild();
+        if (firstChild == null) {
+            return 0;
+        }
+
+        RecyclerView.LayoutParams params = getParams(firstChild);
+        int firstChildPosition = getPosition(firstChild);
+        float previousChildHieght = (float) (getDecoratedMeasuredHeight(firstChild)
+                + params.topMargin + params.bottomMargin);
+
+        // Assume the previous view is the same height as the current one.
+        float percentOfPreviousViewShowing = (getDecoratedTop(firstChild) - params.topMargin)
+                / previousChildHieght;
+        // If the previous view is actually larger than the current one then this the percent
+        // can be greater than 1.
+        percentOfPreviousViewShowing = Math.min(percentOfPreviousViewShowing, 1);
+
+        float currentPosition = (float) firstChildPosition - percentOfPreviousViewShowing;
+
+        int sampleViewHeight = getSampleViewHeight();
+        int availableHeight = getAvailableHeight();
+        int numberOfSampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+        int positionWhenLastItemIsVisible =
+                state.getItemCount() - numberOfSampleViewsThatCanFitOnScreen;
+
+        if (positionWhenLastItemIsVisible <= 0) {
+            return 0;
+        }
+
+        if (currentPosition >= positionWhenLastItemIsVisible) {
+            return SCROLL_RANGE;
+        }
+
+        return (int) (SCROLL_RANGE * currentPosition / positionWhenLastItemIsVisible);
+    }
+
+    /**
+     * The range of the scrollbar can be understood as the granularity of how we want the scrollbar
+     * to scroll.
+     */
+    @Override
+    public int computeVerticalScrollRange(RecyclerView.State state) {
+        return SCROLL_RANGE;
+    }
+
+    @Override
+    public void onAttachedToWindow(RecyclerView view) {
+        super.onAttachedToWindow(view);
+        // The purpose of calling this is so that any animation offsets are re-applied. These are
+        // cleared in View.onDetachedFromWindow().
+        // This fixes b/27672379
+        updatePageBreakPositions();
+        offsetRows();
+    }
+
+    @Override
+    public void onDetachedFromWindow(RecyclerView recyclerView, Recycler recycler) {
+        super.onDetachedFromWindow(recyclerView, recycler);
+    }
+
+    /**
+     * @return The first view that starts on screen. It assumes that it fully fits on the screen
+     *     though. If the first fully visible child is also taller than the screen then it will
+     *     still be returned. However, since the LayoutManager snaps to view starts, having a row
+     *     that tall would lead to a broken experience anyways.
+     */
+    public int getFirstFullyVisibleChildIndex() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+            if (getDecoratedTop(child) - params.topMargin >= getPaddingTop()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @return The position of first visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getFirstFullyVisibleChildPosition() {
+        View child = getFirstFullyVisibleChild();
+        if (child == null) {
+            return -1;
+        }
+        return getPosition(child);
+    }
+
+    /**
+     * @return The position of last visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getLastFullyVisibleChildPosition() {
+        View child = getLastFullyVisibleChild();
+        if (child == null) {
+            return -1;
+        }
+        return getPosition(child);
+    }
+
+    /** @return The first View that is completely visible on-screen. */
+    public View getFirstFullyVisibleChild() {
+        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+        View firstChild = null;
+        if (firstFullyVisibleChildIndex != -1) {
+            firstChild = getChildAt(firstFullyVisibleChildIndex);
+        }
+        return firstChild;
+    }
+
+    /** @return The last View that is completely visible on-screen. */
+    public View getLastFullyVisibleChild() {
+        int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
+        View lastChild = null;
+        if (lastFullyVisibleChildIndex != -1) {
+            lastChild = getChildAt(lastFullyVisibleChildIndex);
+        }
+        return lastChild;
+    }
+
+    /**
+     * @return The last view that ends on screen. It assumes that the start is also on screen
+     *     though. If the last fully visible child is also taller than the screen then it will
+     *     still be returned. However, since the LayoutManager snaps to view starts, having a row
+     *     that tall would lead to a broken experience anyways.
+     */
+    public int getLastFullyVisibleChildIndex() {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+            int childBottom = getDecoratedBottom(child) + params.bottomMargin;
+            int listBottom = getHeight() - getPaddingBottom();
+            if (childBottom <= listBottom) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the index of the child in the list that was last focused and is currently visible to
+     * the user. If no child is found, returns -1.
+     */
+    public int getLastFocusedChildIndexIfVisible() {
+        if (mLastChildPositionToRequestFocus == -1) {
+            return -1;
+        }
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (getPosition(child) == mLastChildPositionToRequestFocus) {
+                RecyclerView.LayoutParams params = getParams(child);
+                int childBottom = getDecoratedBottom(child) + params.bottomMargin;
+                int listBottom = getHeight() - getPaddingBottom();
+                if (childBottom <= listBottom) {
+                    return i;
+                }
+                break;
+            }
+        }
+        return -1;
+    }
+
+    /** @return Whether or not the first view is fully visible. */
+    public boolean isAtTop() {
+        // getFirstFullyVisibleChildIndex() can return -1 which indicates that there are no views
+        // and also means that the list is at the top.
+        return getFirstFullyVisibleChildIndex() <= 0;
+    }
+
+    /** @return Whether or not the last view is fully visible. */
+    public boolean isAtBottom() {
+        int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
+        if (lastFullyVisibleChildIndex == -1) {
+            return true;
+        }
+        View lastFullyVisibleChild = getChildAt(lastFullyVisibleChildIndex);
+        return getPosition(lastFullyVisibleChild) == getItemCount() - 1;
+    }
+
+    /**
+     * Sets whether or not the rows have an offset animation when it scrolls off-screen. The type
+     * of offset is determined by {@link #setRowOffsetMode(int)}.
+     *
+     * <p>A row being offset means that when they reach the top of the screen, the row is flung off
+     * respectively to the rest of the list. This creates a gap between the offset row(s) and the
+     * list.
+     *
+     * @param offsetRows {@code true} if the rows should be offset.
+     */
+    public void setOffsetRows(boolean offsetRows) {
+        mOffsetRows = offsetRows;
+        if (offsetRows) {
+            // Card animation offsets are only needed when we use the flying off the screen effect
+            if (mFlyOffscreenAnimations == null) {
+                mFlyOffscreenAnimations = new LruCache<>(MAX_ANIMATIONS_IN_CACHE);
+            }
+            offsetRows();
+        } else {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                setCardFlyingEffectOffset(getChildAt(i), 0);
+            }
+            mFlyOffscreenAnimations = null;
+        }
+    }
+
+    /**
+     * Sets the manner of offsetting the rows when they are scrolled off-screen. The rows are either
+     * offset individually or the entire page being scrolled off is offset.
+     *
+     * @param mode One of {@link #ROW_OFFSET_MODE_INDIVIDUAL} or {@link #ROW_OFFSET_MODE_PAGE}.
+     */
+    public void setRowOffsetMode(@RowOffsetMode int mode) {
+        if (mode == mRowOffsetMode) {
+            return;
+        }
+
+        mRowOffsetMode = mode;
+        offsetRows();
+    }
+
+    /**
+     * Sets the listener that will be notified of various scroll events in the list.
+     *
+     * @param listener The on-scroll listener.
+     */
+    public void setOnScrollListener(PagedListView.OnScrollListener listener) {
+        mOnScrollListener = listener;
+    }
+
+    /**
+     * Finish the pagination taking into account where the gesture started (not where we are now).
+     *
+     * @return Whether the list was scrolled as a result of the fling.
+     */
+    public boolean settleScrollForFling(RecyclerView parent, int flingVelocity) {
+        if (getChildCount() == 0) {
+            return false;
+        }
+
+        if (mReachedLimitOfDrag) {
+            return false;
+        }
+
+        // If the fling was too slow or too short, settle on the first fully visible row instead.
+        if (Math.abs(flingVelocity) <= FLING_THRESHOLD_TO_PAGINATE
+                || Math.abs(mLastDragDistance) <= DRAG_DISTANCE_TO_PAGINATE) {
+            int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+            if (firstFullyVisibleChildIndex != -1) {
+                int scrollPosition = getPosition(getChildAt(firstFullyVisibleChildIndex));
+                parent.smoothScrollToPosition(scrollPosition);
+                return true;
+            }
+            return false;
+        }
+
+        // Finish the pagination taking into account where the gesture
+        // started (not where we are now).
+        boolean isDownGesture = flingVelocity > 0 || (flingVelocity == 0 && mLastDragDistance >= 0);
+        boolean isUpGesture = flingVelocity < 0 || (flingVelocity == 0 && mLastDragDistance < 0);
+        if (isDownGesture && mLowerPageBreakPosition != -1) {
+            // If the last view is fully visible then only settle on the first fully visible view
+            // instead of the original page down position. However, don't page down if the last
+            // item has come fully into view.
+            parent.smoothScrollToPosition(mAnchorPageBreakPosition);
+            if (mOnScrollListener != null) {
+                mOnScrollListener.onGestureDown();
+            }
+            return true;
+        } else if (isUpGesture && mUpperPageBreakPosition != -1) {
+            parent.smoothScrollToPosition(mUpperPageBreakPosition);
+            if (mOnScrollListener != null) {
+                mOnScrollListener.onGestureUp();
+            }
+            return true;
+        } else {
+            Log.e(
+                    TAG,
+                    "Error setting scroll for fling! flingVelocity: \t"
+                            + flingVelocity
+                            + "\tlastDragDistance: "
+                            + mLastDragDistance
+                            + "\tpageUpAtStartOfDrag: "
+                            + mUpperPageBreakPosition
+                            + "\tpageDownAtStartOfDrag: "
+                            + mLowerPageBreakPosition);
+            // As a last resort, at the last smooth scroller target position if there is one.
+            if (mSmoothScroller != null) {
+                parent.smoothScrollToPosition(mSmoothScroller.getTargetPosition());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** @return The position that paging up from the current position would settle at. */
+    public int getPageUpPosition() {
+        return mUpperPageBreakPosition;
+    }
+
+    /** @return The position that paging down from the current position would settle at. */
+    public int getPageDownPosition() {
+        return mLowerPageBreakPosition;
+    }
+
+    /**
+     * Layout the anchor row. The anchor row is the first fully visible row.
+     *
+     * @param anchorTop The decorated top of the anchor. If it is not known or should be reset to
+     *     the top, pass -1.
+     */
+    private View layoutAnchor(RecyclerView.Recycler recycler, int anchorPosition, int anchorTop) {
+        View anchor = recycler.getViewForPosition(anchorPosition);
+        RecyclerView.LayoutParams params = getParams(anchor);
+        measureChildWithMargins(anchor, 0, 0);
+        int left = getPaddingLeft() + params.leftMargin;
+        int top = (anchorTop == -1) ? params.topMargin : anchorTop;
+        int right = left + getDecoratedMeasuredWidth(anchor);
+        int bottom = top + getDecoratedMeasuredHeight(anchor);
+        layoutDecorated(anchor, left, top, right, bottom);
+        addView(anchor);
+        return anchor;
+    }
+
+    /**
+     * Lays out the next row in the specified direction next to the specified adjacent row.
+     *
+     * @param recycler The recycler from which a new view can be created.
+     * @param adjacentRow The View of the adjacent row which will be used to position the new one.
+     * @param layoutDirection The side of the adjacent row that the new row will be laid out on.
+     * @return The new row that was laid out.
+     */
+    private View layoutNextRow(RecyclerView.Recycler recycler, View adjacentRow,
+            @LayoutDirection int layoutDirection) {
+        int adjacentRowPosition = getPosition(adjacentRow);
+        int newRowPosition = adjacentRowPosition;
+        if (layoutDirection == BEFORE) {
+            newRowPosition = adjacentRowPosition - 1;
+        } else if (layoutDirection == AFTER) {
+            newRowPosition = adjacentRowPosition + 1;
+        }
+
+        // Because we detach all rows in onLayoutChildren, this will often just return a view from
+        // the scrap heap.
+        View newRow = recycler.getViewForPosition(newRowPosition);
+
+        measureChildWithMargins(newRow, 0, 0);
+        RecyclerView.LayoutParams newRowParams =
+                (RecyclerView.LayoutParams) newRow.getLayoutParams();
+        RecyclerView.LayoutParams adjacentRowParams =
+                (RecyclerView.LayoutParams) adjacentRow.getLayoutParams();
+        int left = getPaddingLeft() + newRowParams.leftMargin;
+        int right = left + getDecoratedMeasuredWidth(newRow);
+        int top;
+        int bottom;
+        if (layoutDirection == BEFORE) {
+            bottom = adjacentRow.getTop() - adjacentRowParams.topMargin - newRowParams.bottomMargin;
+            top = bottom - getDecoratedMeasuredHeight(newRow);
+        } else {
+            top = getDecoratedBottom(adjacentRow) + adjacentRowParams.bottomMargin
+                    + newRowParams.topMargin;
+            bottom = top + getDecoratedMeasuredHeight(newRow);
+        }
+        layoutDecorated(newRow, left, top, right, bottom);
+
+        if (layoutDirection == BEFORE) {
+            addView(newRow, 0);
+        } else {
+            addView(newRow);
+        }
+
+        return newRow;
+    }
+
+    /** @return Whether another row should be laid out in the specified direction. */
+    private boolean shouldLayoutNextRow(
+            RecyclerView.State state, View adjacentRow, @LayoutDirection int layoutDirection) {
+        int adjacentRowPosition = getPosition(adjacentRow);
+
+        if (layoutDirection == BEFORE) {
+            if (adjacentRowPosition == 0) {
+                // We already laid out the first row.
+                return false;
+            }
+        } else if (layoutDirection == AFTER) {
+            if (adjacentRowPosition >= state.getItemCount() - 1) {
+                // We already laid out the last row.
+                return false;
+            }
+        }
+
+        // If we are scrolling layout views until the target position.
+        if (mSmoothScroller != null) {
+            if (layoutDirection == BEFORE
+                    && adjacentRowPosition >= mSmoothScroller.getTargetPosition()) {
+                return true;
+            } else if (layoutDirection == AFTER
+                    && adjacentRowPosition <= mSmoothScroller.getTargetPosition()) {
+                return true;
+            }
+        }
+
+        View focusedRow = getFocusedChild();
+        if (focusedRow != null) {
+            int focusedRowPosition = getPosition(focusedRow);
+            if (layoutDirection == BEFORE && adjacentRowPosition
+                    >= focusedRowPosition - NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+                return true;
+            } else if (layoutDirection == AFTER && adjacentRowPosition
+                    <= focusedRowPosition + NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+                return true;
+            }
+        }
+
+        RecyclerView.LayoutParams params = getParams(adjacentRow);
+        int adjacentRowTop = getDecoratedTop(adjacentRow) - params.topMargin;
+        int adjacentRowBottom = getDecoratedBottom(adjacentRow) - params.bottomMargin;
+        if (layoutDirection == BEFORE && adjacentRowTop < getPaddingTop() - getHeight()) {
+            // View is more than 1 page past the top of the screen and also past where the user has
+            // scrolled to. We want to keep one page past the top to make the scroll up calculation
+            // easier and scrolling smoother.
+            return false;
+        } else if (layoutDirection == AFTER
+                && adjacentRowBottom > getHeight() - getPaddingBottom()) {
+            // View is off of the bottom and also past where the user has scrolled to.
+            return false;
+        }
+
+        return true;
+    }
+
+    /** Remove and recycle views that are no longer needed. */
+    private void recycleChildrenFromStart(RecyclerView.Recycler recycler) {
+        // Start laying out children one page before the top of the viewport.
+        int childrenStart = getPaddingTop() - getHeight();
+
+        int focusedChildPosition = Integer.MAX_VALUE;
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            focusedChildPosition = getPosition(focusedChild);
+        }
+
+        // Count the number of views that should be removed.
+        int detachedCount = 0;
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            int childEnd = getDecoratedBottom(child);
+            int childPosition = getPosition(child);
+
+            if (childEnd >= childrenStart || childPosition >= focusedChildPosition - 1) {
+                break;
+            }
+
+            detachedCount++;
+        }
+
+        // Remove the number of views counted above. Done by removing the first child n times.
+        while (--detachedCount >= 0) {
+            final View child = getChildAt(0);
+            removeAndRecycleView(child, recycler);
+        }
+    }
+
+    /** Remove and recycle views that are no longer needed. */
+    private void recycleChildrenFromEnd(RecyclerView.Recycler recycler) {
+        // Layout views until the end of the viewport.
+        int childrenEnd = getHeight();
+
+        int focusedChildPosition = Integer.MIN_VALUE + 1;
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            focusedChildPosition = getPosition(focusedChild);
+        }
+
+        // Count the number of views that should be removed.
+        int firstDetachedPos = 0;
+        int detachedCount = 0;
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            final View child = getChildAt(i);
+            int childStart = getDecoratedTop(child);
+            int childPosition = getPosition(child);
+
+            if (childStart <= childrenEnd || childPosition <= focusedChildPosition - 1) {
+                break;
+            }
+
+            firstDetachedPos = i;
+            detachedCount++;
+        }
+
+        while (--detachedCount >= 0) {
+            final View child = getChildAt(firstDetachedPos);
+            removeAndRecycleView(child, recycler);
+        }
+    }
+
+    /**
+     * Offset rows to do fancy animations. If offset rows was not enabled with
+     * {@link #setOffsetRows}, this will do nothing.
+     *
+     * @see #offsetRowsIndividually
+     * @see #offsetRowsByPage
+     * @see #setOffsetRows
+     */
+    public void offsetRows() {
+        if (!mOffsetRows) {
+            return;
+        }
+
+        if (mRowOffsetMode == ROW_OFFSET_MODE_PAGE) {
+            offsetRowsByPage();
+        } else if (mRowOffsetMode == ROW_OFFSET_MODE_INDIVIDUAL) {
+            offsetRowsIndividually();
+        }
+    }
+
+    /**
+     * Offset the single row that is scrolling off the screen such that by the time the next row
+     * reaches the top, it will have accelerated completely off of the screen.
+     */
+    private void offsetRowsIndividually() {
+        if (getChildCount() == 0) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, ":: offsetRowsIndividually getChildCount=0");
+            }
+            return;
+        }
+
+        // Identify the dangling row. It will be the first row that is at the top of the
+        // list or above.
+        int danglingChildIndex = -1;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (getDecoratedTop(child) - getParams(child).topMargin <= getPaddingTop()) {
+                danglingChildIndex = i;
+                break;
+            }
+        }
+
+        mAnchorPageBreakPosition = danglingChildIndex;
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, ":: offsetRowsIndividually danglingChildIndex: " + danglingChildIndex);
+        }
+
+        // Calculate the total amount that the view will need to scroll in order to go completely
+        // off screen.
+        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+        int[] locs = new int[2];
+        rv.getLocationInWindow(locs);
+        int listTopInWindow = locs[1] + rv.getPaddingTop();
+        int maxDanglingViewTranslation;
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+
+            maxDanglingViewTranslation = listTopInWindow;
+            // If the child has a negative margin, we'll actually need to translate the view a
+            // little but further to get it completely off screen.
+            if (params.topMargin < 0) {
+                maxDanglingViewTranslation -= params.topMargin;
+            }
+            if (params.bottomMargin < 0) {
+                maxDanglingViewTranslation -= params.bottomMargin;
+            }
+
+            if (i < danglingChildIndex) {
+                child.setAlpha(0f);
+            } else if (i > danglingChildIndex) {
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, 0);
+            } else {
+                int totalScrollDistance =
+                        getDecoratedMeasuredHeight(child) + params.topMargin + params.bottomMargin;
+
+                int distanceLeftInScroll =
+                        getDecoratedBottom(child) + params.bottomMargin - getPaddingTop();
+                float percentageIntoScroll = 1 - distanceLeftInScroll / (float) totalScrollDistance;
+                float interpolatedPercentage =
+                        mDanglingRowInterpolator.getInterpolation(percentageIntoScroll);
+
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, -(maxDanglingViewTranslation
+                        * interpolatedPercentage));
+            }
+        }
+    }
+
+    /**
+     * When the list scrolls, the entire page of rows will offset in one contiguous block. This
+     * significantly reduces the amount of extra motion at the top of the screen.
+     */
+    private void offsetRowsByPage() {
+        View anchorView = findViewByPosition(mAnchorPageBreakPosition);
+        if (anchorView == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, ":: offsetRowsByPage anchorView null");
+            }
+            return;
+        }
+        int anchorViewTop = getDecoratedTop(anchorView) - getParams(anchorView).topMargin;
+
+        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+        int upperViewTop =
+                getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
+
+        int scrollDistance = upperViewTop - anchorViewTop;
+
+        int distanceLeft = anchorViewTop - getPaddingTop();
+        float scrollPercentage =
+                (Math.abs(scrollDistance) - distanceLeft) / (float) Math.abs(scrollDistance);
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: offsetRowsByPage scrollDistance:%s, distanceLeft:%s, "
+                            + "scrollPercentage:%s",
+                    scrollDistance, distanceLeft, scrollPercentage));
+        }
+
+        // Calculate the total amount that the view will need to scroll in order to go completely
+        // off screen.
+        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+        int[] locs = new int[2];
+        rv.getLocationInWindow(locs);
+        int listTopInWindow = locs[1] + rv.getPaddingTop();
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            int position = getPosition(child);
+            if (position < mUpperPageBreakPosition) {
+                child.setAlpha(0f);
+                setCardFlyingEffectOffset(child, -listTopInWindow);
+            } else if (position < mAnchorPageBreakPosition) {
+                // If the child has a negative margin, we need to offset the row by a little bit
+                // extra so that it moves completely off screen.
+                RecyclerView.LayoutParams params = getParams(child);
+                int extraTranslation = 0;
+                if (params.topMargin < 0) {
+                    extraTranslation -= params.topMargin;
+                }
+                if (params.bottomMargin < 0) {
+                    extraTranslation -= params.bottomMargin;
+                }
+                int translation = (int) ((listTopInWindow + extraTranslation)
+                        * mDanglingRowInterpolator.getInterpolation(scrollPercentage));
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, -translation);
+            } else {
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, 0);
+            }
+        }
+    }
+
+    /**
+     * Apply an offset to this view. This offset is applied post-layout so it doesn't affect when
+     * views are recycled
+     *
+     * @param child The view to apply this to
+     * @param verticalOffset The offset for this child.
+     */
+    private void setCardFlyingEffectOffset(View child, float verticalOffset) {
+        // Ideally instead of doing all this, we could use View.setTranslationY(). However, the
+        // default RecyclerView.ItemAnimator also uses this method which causes layout issues.
+        // See: http://b/25977087
+        TranslateAnimation anim = mFlyOffscreenAnimations.get(child);
+        if (anim == null) {
+            anim = new TranslateAnimation();
+            anim.setFillEnabled(true);
+            anim.setFillAfter(true);
+            anim.setDuration(0);
+            mFlyOffscreenAnimations.put(child, anim);
+        } else if (anim.verticalOffset == verticalOffset) {
+            return;
+        }
+
+        anim.reset();
+        anim.verticalOffset = verticalOffset;
+        anim.setStartTime(Animation.START_ON_FIRST_FRAME);
+        child.setAnimation(anim);
+        anim.startNow();
+    }
+
+    /**
+     * Update the page break positions based on the position of the views on screen. This should be
+     * called whenever view move or change such as during a scroll or layout.
+     */
+    private void updatePageBreakPositions() {
+        if (getChildCount() == 0) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, ":: updatePageBreakPosition getChildCount: 0");
+            }
+            return;
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: #BEFORE updatePageBreakPositions "
+                            + "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
+                            + "mLowerPageBreakPosition:%s",
+                            mAnchorPageBreakPosition, mUpperPageBreakPosition,
+                            mLowerPageBreakPosition));
+        }
+
+        mAnchorPageBreakPosition = getPosition(getFirstFullyVisibleChild());
+
+        if (mAnchorPageBreakPosition == -1) {
+            Log.w(TAG, "Unable to update anchor positions. There is no anchor position.");
+            return;
+        }
+
+        View anchorPageBreakView = findViewByPosition(mAnchorPageBreakPosition);
+        if (anchorPageBreakView == null) {
+            return;
+        }
+        int topMargin = getParams(anchorPageBreakView).topMargin;
+        int anchorTop = getDecoratedTop(anchorPageBreakView) - topMargin;
+        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+        int upperPageBreakTop = upperPageBreakView == null
+                ? Integer.MIN_VALUE
+                : getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: #MID updatePageBreakPositions topMargin:%s, anchorTop:%s"
+                            + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s,"
+                            + " mLowerPageBreakPosition:%s",
+                            topMargin,
+                            anchorTop,
+                            mAnchorPageBreakPosition,
+                            mUpperPageBreakPosition,
+                            mLowerPageBreakPosition));
+        }
+
+        if (anchorTop < getPaddingTop()) {
+            // The anchor has moved above the viewport. We are now on the next page. Shift the page
+            // break positions and calculate a new lower one.
+            mUpperPageBreakPosition = mAnchorPageBreakPosition;
+            mAnchorPageBreakPosition = mLowerPageBreakPosition;
+            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+        } else if (mAnchorPageBreakPosition > 0 && upperPageBreakTop >= getPaddingTop()) {
+            // The anchor has moved below the viewport. We are now on the previous page. Shift
+            // the page break positions and calculate a new upper one.
+            mLowerPageBreakPosition = mAnchorPageBreakPosition;
+            mAnchorPageBreakPosition = mUpperPageBreakPosition;
+            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+        } else {
+            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: #AFTER updatePageBreakPositions"
+                            + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s,"
+                            + " mLowerPageBreakPosition:%s",
+                            mAnchorPageBreakPosition, mUpperPageBreakPosition,
+                    mLowerPageBreakPosition));
+        }
+    }
+
+    /**
+     * @return The page break position of the page before the anchor page break position. However,
+     *     if it reaches the end of the laid out children or position 0, it will just return that.
+     */
+    @VisibleForTesting
+    int calculatePreviousPageBreakPosition(int position) {
+        if (position == -1) {
+            return -1;
+        }
+        View referenceView = findViewByPosition(position);
+        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+        int previousPagePosition = position;
+        while (previousPagePosition > 0) {
+            previousPagePosition--;
+            View child = findViewByPosition(previousPagePosition);
+            if (child == null) {
+                // View has not been laid out yet.
+                return previousPagePosition + 1;
+            }
+
+            int childTop = getDecoratedTop(child) - getParams(child).topMargin;
+            if (childTop < referenceViewTop - getHeight()) {
+                return previousPagePosition + 1;
+            }
+        }
+        // Beginning of the list.
+        return 0;
+    }
+
+    /**
+     * @return The page break position of the next page after the anchor page break position.
+     *     However, if it reaches the end of the laid out children or end of the list, it will just
+     *     return that.
+     */
+    @VisibleForTesting
+    int calculateNextPageBreakPosition(int position) {
+        if (position == -1) {
+            return -1;
+        }
+
+        View referenceView = findViewByPosition(position);
+        if (referenceView == null) {
+            return position;
+        }
+        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+        int nextPagePosition = position;
+
+        // Search for the first child item after the referenceView that didn't fully fit on to the
+        // screen. The next page should start from the item before this child, so that users have
+        // a visual anchoring point of the page change.
+        while (nextPagePosition < getItemCount() - 1) {
+            nextPagePosition++;
+            View child = findViewByPosition(nextPagePosition);
+            if (child == null) {
+                // The next view has not been laid out yet.
+                return nextPagePosition - 1;
+            }
+
+            int childTop = getDecoratedTop(child) - getParams(child).topMargin;
+            if (childTop > referenceViewTop + getHeight()) {
+                // If choosing the previous child causes the view to snap back to the referenceView
+                // position, then skip that and go directly to the child. This avoids the case
+                // where a tall card in the layout causes the view to constantly snap back to
+                // the top when scrolled.
+                return nextPagePosition - 1 == position ? nextPagePosition : nextPagePosition - 1;
+            }
+        }
+        // End of the list.
+        return nextPagePosition;
+    }
+
+    /**
+     * In this style, the focus will scroll down to the middle of the screen and lock there so that
+     * moving in either direction will move the entire list by 1.
+     */
+    private boolean onRequestChildFocusMarioStyle(RecyclerView parent, View child) {
+        int focusedPosition = getPosition(child);
+        if (focusedPosition == mLastChildPositionToRequestFocus) {
+            return true;
+        }
+        mLastChildPositionToRequestFocus = focusedPosition;
+
+        int availableHeight = getAvailableHeight();
+        int focusedChildTop = getDecoratedTop(child);
+        int focusedChildBottom = getDecoratedBottom(child);
+
+        int childIndex = parent.indexOfChild(child);
+        // Iterate through children starting at the focused child to find the child above it to
+        // smooth scroll to such that the focused child will be as close to the middle of the screen
+        // as possible.
+        for (int i = childIndex; i >= 0; i--) {
+            View childAtI = getChildAt(i);
+            if (childAtI == null) {
+                Log.e(TAG, "Child is null at index " + i);
+                continue;
+            }
+            // We haven't found a view that is more than half of the recycler view height above it
+            // but we've reached the top so we can't go any further.
+            if (i == 0) {
+                parent.smoothScrollToPosition(getPosition(childAtI));
+                break;
+            }
+
+            // Because we want to scroll to the first view that is less than half of the screen
+            // away from the focused view, we "look ahead" one view. When the look ahead view
+            // is more than availableHeight / 2 away, the current child at i is the one we want to
+            // scroll to. However, sometimes, that view can be null (ie, if the view is in
+            // transition). In that case, just skip that view.
+
+            View childBefore = getChildAt(i - 1);
+            if (childBefore == null) {
+                continue;
+            }
+            int distanceToChildBeforeFromTop = focusedChildTop - getDecoratedTop(childBefore);
+            int distanceToChildBeforeFromBottom = focusedChildBottom - getDecoratedTop(childBefore);
+
+            if (distanceToChildBeforeFromTop > availableHeight / 2
+                    || distanceToChildBeforeFromBottom > availableHeight) {
+                parent.smoothScrollToPosition(getPosition(childAtI));
+                break;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * We don't actually know the size of every single view, only what is currently laid out. This
+     * makes it difficult to do accurate scrollbar calculations. However, lists in the car often
+     * consist of views with identical heights. Because of that, we can use a single sample view to
+     * do our calculations for. The main exceptions are in the first items of a list (hero card,
+     * last call card, etc) so if the first view is at position 0, we pick the next one.
+     *
+     * @return The decorated measured height of the sample view plus its margins.
+     */
+    private int getSampleViewHeight() {
+        if (mSampleViewHeight != -1) {
+            return mSampleViewHeight;
+        }
+        int sampleViewIndex = getFirstFullyVisibleChildIndex();
+        View sampleView = getChildAt(sampleViewIndex);
+        if (getPosition(sampleView) == 0 && sampleViewIndex < getChildCount() - 1) {
+            sampleView = getChildAt(++sampleViewIndex);
+        }
+        RecyclerView.LayoutParams params = getParams(sampleView);
+        int height = getDecoratedMeasuredHeight(sampleView) + params.topMargin
+                + params.bottomMargin;
+        if (height == 0) {
+            // This can happen if the view isn't measured yet.
+            Log.w(
+                    TAG,
+                    "The sample view has a height of 0. Returning a dummy value for now "
+                            + "that won't be cached.");
+            height = mContext.getResources().getDimensionPixelSize(R.dimen.car_sample_row_height);
+        } else {
+            mSampleViewHeight = height;
+        }
+        return height;
+    }
+
+    /** @return The height of the RecyclerView excluding padding. */
+    private int getAvailableHeight() {
+        return getHeight() - getPaddingTop() - getPaddingBottom();
+    }
+
+    /**
+     * @return {@link RecyclerView.LayoutParams} for the given view or null if it isn't a child of
+     *     {@link RecyclerView}.
+     */
+    private static RecyclerView.LayoutParams getParams(View view) {
+        return (RecyclerView.LayoutParams) view.getLayoutParams();
+    }
+
+    /**
+     * Custom {@link LinearSmoothScroller} that has: a) Custom control over the speed of scrolls. b)
+     * Scrolling snaps to start. All of our scrolling logic depends on that. c) Keeps track of some
+     * state of the current scroll so that can aid in things like the scrollbar calculations.
+     */
+    private final class CarSmoothScroller extends LinearSmoothScroller {
+        /** This value (150) was hand tuned by UX for what felt right. * */
+        private static final float MILLISECONDS_PER_INCH = 150f;
+        /** This value (0.45) was hand tuned by UX for what felt right. * */
+        private static final float DECELERATION_TIME_DIVISOR = 0.45f;
+
+        /** This value (1.8) was hand tuned by UX for what felt right. * */
+        private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f);
+
+        private final int mTargetPosition;
+
+        CarSmoothScroller(Context context, int targetPosition) {
+            super(context);
+            mTargetPosition = targetPosition;
+        }
+
+        @Override
+        public PointF computeScrollVectorForPosition(int i) {
+            if (getChildCount() == 0) {
+                return null;
+            }
+            final int firstChildPos = getPosition(getChildAt(getFirstFullyVisibleChildIndex()));
+            final int direction = (mTargetPosition < firstChildPos) ? -1 : 1;
+            return new PointF(0, direction);
+        }
+
+        @Override
+        protected int getVerticalSnapPreference() {
+            // This is key for most of the scrolling logic that guarantees that scrolling
+            // will settle with a view aligned to the top.
+            return LinearSmoothScroller.SNAP_TO_START;
+        }
+
+        @Override
+        protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+            int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START);
+            if (dy == 0) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Scroll distance is 0");
+                }
+                return;
+            }
+
+            final int time = calculateTimeForDeceleration(dy);
+            if (time > 0) {
+                action.update(0, -dy, time, mInterpolator);
+            }
+        }
+
+        @Override
+        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+            return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+        }
+
+        @Override
+        protected int calculateTimeForDeceleration(int dx) {
+            return (int) Math.ceil(calculateTimeForScrolling(dx) / DECELERATION_TIME_DIVISOR);
+        }
+
+        @Override
+        public int getTargetPosition() {
+            return mTargetPosition;
+        }
+    }
+
+    /**
+     * Animation that translates a view by the specified amount. Used for card flying off the screen
+     * effect.
+     */
+    private static class TranslateAnimation extends Animation {
+        public float verticalOffset;
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            super.applyTransformation(interpolatedTime, t);
+            t.getMatrix().setTranslate(0, verticalOffset);
+        }
+    }
+}
diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java
new file mode 100644
index 0000000..edc3241
--- /dev/null
+++ b/android/support/car/widget/CarRecyclerView.java
@@ -0,0 +1,204 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate.
+ *
+ * <p>It also has the ability to fade children as they scroll off screen that can be set with {@link
+ * #setFadeLastItem(boolean)}.
+ */
+public class CarRecyclerView extends RecyclerView {
+    private static final String PARCEL_CLASS = "android.os.Parcel";
+    private static final String SAVED_STATE_CLASS =
+            "android.support.v7.widget.RecyclerView.SavedState";
+    private boolean mFadeLastItem;
+    private Constructor<?> mSavedStateConstructor;
+    /**
+     * If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be
+     * called. However, we want to make sure that the list still snaps to the next page when this
+     * happens.
+     */
+    private boolean mWasFlingCalledForGesture;
+
+    public CarRecyclerView(Context context) {
+        this(context, null);
+    }
+
+    public CarRecyclerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CarRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setFocusableInTouchMode(false);
+        setFocusable(false);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state.getClass().getClassLoader() != getClass().getClassLoader()) {
+            if (mSavedStateConstructor == null) {
+                mSavedStateConstructor = getSavedStateConstructor();
+            }
+            // Class loader mismatch, recreate from parcel.
+            Parcel obtain = Parcel.obtain();
+            state.writeToParcel(obtain, 0);
+            try {
+                Parcelable newState = (Parcelable) mSavedStateConstructor.newInstance(obtain);
+                super.onRestoreInstanceState(newState);
+            } catch (InstantiationException
+                    | IllegalAccessException
+                    | IllegalArgumentException
+                    | InvocationTargetException e) {
+                // Fail loudy here.
+                throw new RuntimeException(e);
+            }
+        } else {
+            super.onRestoreInstanceState(state);
+        }
+    }
+
+    @Override
+    public boolean fling(int velocityX, int velocityY) {
+        mWasFlingCalledForGesture = true;
+        return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        // We want the parent to handle all touch events. There's a lot going on there,
+        // and there is no reason to overwrite that functionality. If we do, bad things will happen.
+        final boolean ret = super.onTouchEvent(e);
+
+        int action = e.getActionMasked();
+        if (action == MotionEvent.ACTION_UP) {
+            if (!mWasFlingCalledForGesture) {
+                ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
+            }
+            mWasFlingCalledForGesture = false;
+        }
+
+        return ret;
+    }
+
+    @Override
+    public boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
+        if (mFadeLastItem) {
+            float onScreen = 1f;
+            if ((child.getTop() < getBottom() && child.getBottom() > getBottom())) {
+                onScreen = ((float) (getBottom() - child.getTop())) / (float) child.getHeight();
+            } else if ((child.getTop() < getTop() && child.getBottom() > getTop())) {
+                onScreen = ((float) (child.getBottom() - getTop())) / (float) child.getHeight();
+            }
+            float alpha = 1 - (1 - onScreen) * (1 - onScreen);
+            fadeChild(child, alpha);
+        }
+
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    public void setFadeLastItem(boolean fadeLastItem) {
+        mFadeLastItem = fadeLastItem;
+    }
+
+    /**
+     * Scrolls the contents of this {@link CarRecyclerView} up one page. A page is defined as the
+     * number of items that fit completely on the screen.
+     */
+    public void pageUp() {
+        CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+        int pageUpPosition = lm.getPageUpPosition();
+        if (pageUpPosition == -1) {
+            return;
+        }
+
+        smoothScrollToPosition(pageUpPosition);
+    }
+
+    /**
+     * Scrolls the contents of this {@link CarRecyclerView} down one page. A page is defined as the
+     * number of items that fit completely on the screen.
+     */
+    public void pageDown() {
+        CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+        int pageDownPosition = lm.getPageDownPosition();
+        if (pageDownPosition == -1) {
+            return;
+        }
+
+        smoothScrollToPosition(pageDownPosition);
+    }
+
+    /** Sets {@link #mSavedStateConstructor} to private SavedState constructor. */
+    private Constructor<?> getSavedStateConstructor() {
+        Class<?> savedStateClass = null;
+        // Find package private subclass RecyclerView$SavedState.
+        for (Class<?> c : RecyclerView.class.getDeclaredClasses()) {
+            if (c.getCanonicalName().equals(SAVED_STATE_CLASS)) {
+                savedStateClass = c;
+                break;
+            }
+        }
+        if (savedStateClass == null) {
+            throw new RuntimeException("RecyclerView$SavedState not found!");
+        }
+        // Find constructor that takes a {@link Parcel}.
+        for (Constructor<?> c : savedStateClass.getDeclaredConstructors()) {
+            Class<?>[] parameterTypes = c.getParameterTypes();
+            if (parameterTypes.length == 1
+                    && parameterTypes[0].getCanonicalName().equals(PARCEL_CLASS)) {
+                mSavedStateConstructor = c;
+                mSavedStateConstructor.setAccessible(true);
+                break;
+            }
+        }
+        if (mSavedStateConstructor == null) {
+            throw new RuntimeException("RecyclerView$SavedState constructor not found!");
+        }
+        return mSavedStateConstructor;
+    }
+
+    /**
+     * Fades child by alpha. If child is a {@link ViewGroup} then it will recursively fade its
+     * children instead.
+     */
+    private void fadeChild(@NonNull View child, float alpha) {
+        if (child instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) child;
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                fadeChild(vg.getChildAt(i), alpha);
+            }
+        } else {
+            child.setAlpha(alpha);
+        }
+    }
+}
diff --git a/android/support/car/widget/ColumnCardView.java b/android/support/car/widget/ColumnCardView.java
new file mode 100644
index 0000000..06f8553
--- /dev/null
+++ b/android/support/car/widget/ColumnCardView.java
@@ -0,0 +1,115 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.car.R;
+import android.support.car.utils.ColumnCalculator;
+import android.support.v7.widget.CardView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * A {@link CardView} whose width can be specified by the number of columns that it will span.
+ *
+ * <p>The {@code ColumnCardView} works similarly to a regular {@link CardView}, except that
+ * its {@code layout_width} attribute is always ignored. Instead, its width is automatically
+ * calculated based on a specified {@code columnSpan} attribute. Alternatively, a user can call
+ * {@link #setColumnSpan(int)}. If no column span is given, the {@code ColumnCardView} will have
+ * a default span value that it uses.
+ *
+ * <pre>
+ * &lt;android.support.car.widget.ColumnCardView
+ *     android:layout_width="wrap_content"
+ *     android:layout_height="wrap_content"
+ *     app:columnSpan="4" /&gt;
+ * </pre>
+ *
+ * @see ColumnCalculator
+ */
+public final class ColumnCardView extends CardView {
+    private static final String TAG = "ColumnCardView";
+
+    private ColumnCalculator mColumnCalculator;
+    private int mColumnSpan;
+
+    public ColumnCardView(Context context) {
+        super(context);
+        init(context, null, 0 /* defStyleAttrs */);
+    }
+
+    public ColumnCardView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs, 0 /* defStyleAttrs */);
+    }
+
+    public ColumnCardView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs, defStyleAttr);
+    }
+
+    private void init(Context context, AttributeSet attrs, int defStyleAttrs) {
+        mColumnCalculator = ColumnCalculator.getInstance(context);
+
+        int defaultColumnSpan = getResources().getInteger(
+                R.integer.column_card_default_column_span);
+
+        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColumnCardView,
+                defStyleAttrs, 0 /* defStyleRes */);
+        mColumnSpan = ta.getInteger(R.styleable.ColumnCardView_columnSpan, defaultColumnSpan);
+        ta.recycle();
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Column span: " + mColumnSpan);
+        }
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Override any specified width so that the width is one that is calculated based on
+        // column and gutter span.
+        int width = mColumnCalculator.getSizeForColumnSpan(mColumnSpan);
+        super.onMeasure(
+                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                heightMeasureSpec);
+    }
+
+    /**
+     * Sets the number of columns that this {@code ColumnCardView} will span. The given span is
+     * ignored if it is less than 0 or greater than the number of columns that fit on screen.
+     *
+     * @param columnSpan The number of columns this {@code ColumnCardView} will span across.
+     */
+    public void setColumnSpan(int columnSpan) {
+        if (columnSpan <= 0 || columnSpan > mColumnCalculator.getNumOfColumns()) {
+            return;
+        }
+
+        mColumnSpan = columnSpan;
+        requestLayout();
+    }
+
+    /**
+     * Returns the currently number of columns that this {@code ColumnCardView} spans.
+     *
+     * @return The number of columns this {@code ColumnCardView} spans across.
+     */
+    public int getColumnSpan() {
+        return mColumnSpan;
+    }
+}
diff --git a/android/support/car/widget/DayNightStyle.java b/android/support/car/widget/DayNightStyle.java
new file mode 100644
index 0000000..ff5a1b3
--- /dev/null
+++ b/android/support/car/widget/DayNightStyle.java
@@ -0,0 +1,66 @@
+/*
+ * 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.widget;
+
+import android.support.annotation.IntDef;
+
+/**
+ * Specifies how the system UI should respond to day/night mode events.
+ *
+ * <p>By default, the Android Auto system UI assumes the app content background is light during the
+ * day and dark during the night. The system UI updates the foreground color (such as status bar
+ * icon colors) to be dark during day mode and light during night mode. By setting the
+ * DayNightStyle, the app can specify how the system should respond to a day/night mode event. For
+ * example, if the app has a dark content background for both day and night time, the app can tell
+ * the system to use {@link #FORCE_NIGHT} style so the foreground color is locked to light color for
+ * both cases.
+ *
+ * <p>Note: Not all system UI elements can be customized with a DayNightStyle.
+ */
+@IntDef({
+        DayNightStyle.AUTO,
+        DayNightStyle.AUTO_INVERSE,
+        DayNightStyle.FORCE_NIGHT,
+        DayNightStyle.FORCE_DAY,
+})
+public @interface DayNightStyle {
+    /**
+     * Sets the foreground color to be automatically changed based on day/night mode, assuming the
+     * app content background is light during the day and dark during the night.
+     *
+     * <p>This is the default behavior.
+     */
+    int AUTO = 0;
+
+    /**
+     * Sets the foreground color to be automatically changed based on day/night mode, assuming the
+     * app content background is dark during the day and light during the night.
+     */
+    int AUTO_INVERSE = 1;
+
+    /**
+     * Sets the foreground color to be locked to the night version, which assumes the app content
+     * background is always dark during both day and night.
+     */
+    int FORCE_NIGHT = 2;
+
+    /**
+     * Sets the foreground color to be locked to the day version, which assumes the app content
+     * background is always light during both day and night.
+     */
+    int FORCE_DAY = 3;
+}
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
new file mode 100644
index 0000000..8527c65
--- /dev/null
+++ b/android/support/car/widget/PagedListView.java
@@ -0,0 +1,854 @@
+/*
+ * 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.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Handler;
+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;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that
+ * resembles a {@link android.widget.ListView} but also has page up and page down arrows on the
+ * right side.
+ */
+public class PagedListView extends FrameLayout {
+    /** Default maximum number of clicks allowed on a list */
+    public static final int DEFAULT_MAX_CLICKS = 6;
+
+    /**
+     * The amount of time after settling to wait before autoscrolling to the next page when the user
+     * holds down a pagination button.
+     */
+    protected static final int PAGINATION_HOLD_DELAY_MS = 400;
+
+    private static final String TAG = "PagedListView";
+    private static final int INVALID_RESOURCE_ID = -1;
+
+    protected final CarRecyclerView mRecyclerView;
+    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;
+    protected RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
+
+    /** Maximum number of pages to show. Values < 0 show all pages. */
+    private int mMaxPages = -1;
+
+    protected OnScrollListener mOnScrollListener;
+
+    /** Number of visible rows per page */
+    private int mDefaultMaxPages = DEFAULT_MAX_CLICKS;
+
+    /** Used to check if there are more items added to the list. */
+    private int mLastItemCount = 0;
+
+    private boolean mNeedsFocus;
+
+    /**
+     * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to cap the number of
+     * items.
+     *
+     * <p>NOTE: it is still up to the adapter to use maxItems in {@link
+     * android.support.v7.widget.RecyclerView.Adapter#getItemCount()}.
+     *
+     * <p>the recommended way would be with:
+     *
+     * <pre>{@code
+     * {@literal@}Override
+     * public int getItemCount() {
+     *   return Math.min(super.getItemCount(), mMaxItems);
+     * }
+     * }</pre>
+     */
+    public interface ItemCap {
+        /**
+         * Sets the maximum number of items available in the adapter. A value less than '0' means
+         * the list should not be capped.
+         */
+        void setMaxItems(int maxItems);
+    }
+
+    /**
+     * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to set the position
+     * offset for the adapter to load the data.
+     *
+     * <p>For example in the adapter, if the positionOffset is 20, then for position 0 it will show
+     * the item in position 20 instead, for position 1 it will show the item in position 21 instead
+     * and so on.
+     */
+    // TODO(b/28003781): ItemPositionOffset and ItemCap interfaces should be merged once
+    // we enable AlphaJump outside drawer.
+    public interface ItemPositionOffset {
+        /** Sets the position offset for the adapter. */
+        void setPositionOffset(int positionOffset);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs) {
+        this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+        this(context, attrs, defStyleAttrs, defStyleRes, 0);
+    }
+
+    public PagedListView(
+            Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes, int layoutId) {
+        super(context, attrs, defStyleAttrs, defStyleRes);
+        if (layoutId == 0) {
+            layoutId = R.layout.car_paged_recycler_view;
+        }
+        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);
+        boolean fadeLastItem = a.getBoolean(R.styleable.PagedListView_fadeLastItem, false);
+        mRecyclerView.setFadeLastItem(fadeLastItem);
+        boolean offsetRows = a.getBoolean(R.styleable.PagedListView_offsetRows, false);
+
+        mMaxPages = getDefaultMaxPages();
+
+        mLayoutManager = new CarLayoutManager(context);
+        mLayoutManager.setOffsetRows(offsetRows);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
+        mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
+        mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager));
+
+        if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) {
+            int dividerStartMargin = a.getDimensionPixelSize(
+                    R.styleable.PagedListView_dividerStartMargin, 0);
+            int dividerStartId = a.getResourceId(
+                    R.styleable.PagedListView_alignDividerStartTo, INVALID_RESOURCE_ID);
+            int dividerEndId = a.getResourceId(
+                    R.styleable.PagedListView_alignDividerEndTo, INVALID_RESOURCE_ID);
+
+            mRecyclerView.addItemDecoration(new DividerDecoration(context, dividerStartMargin,
+                    dividerStartId, dividerEndId));
+        }
+
+        // Set this to true so that this view consumes clicks events and views underneath
+        // don't receive this click event. Without this it's possible to click places in the
+        // view that don't capture the event, and as a result, elements visually hidden consume
+        // the event.
+        setClickable(true);
+
+        // Set focusable false explicitly to handle the behavior change in Android O where
+        // clickable view becomes focusable by default.
+        setFocusable(false);
+
+        mScrollBarEnabled = a.getBoolean(R.styleable.PagedListView_scrollBarEnabled, true);
+        mScrollBarView = (PagedScrollBarView) findViewById(R.id.paged_scroll_view);
+        mScrollBarView.setPaginationListener(
+                new PagedScrollBarView.PaginationListener() {
+                    @Override
+                    public void onPaginate(int direction) {
+                        if (direction == PagedScrollBarView.PaginationListener.PAGE_UP) {
+                            mRecyclerView.pageUp();
+                            if (mOnScrollListener != null) {
+                                mOnScrollListener.onScrollUpButtonClicked();
+                            }
+                        } else if (direction == PagedScrollBarView.PaginationListener.PAGE_DOWN) {
+                            mRecyclerView.pageDown();
+                            if (mOnScrollListener != null) {
+                                mOnScrollListener.onScrollDownButtonClicked();
+                            }
+                        } else {
+                            Log.e(TAG, "Unknown pagination direction (" + direction + ")");
+                        }
+                    }
+                });
+        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);
+        }
+
+        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();
+        mHandler.removeCallbacks(mUpdatePaginationRunnable);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent e) {
+        if (e.getAction() == MotionEvent.ACTION_DOWN) {
+            // The user has interacted with the list using touch. All movements will now paginate
+            // the list.
+            mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_PAGE);
+        }
+        return super.onInterceptTouchEvent(e);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        // The user has interacted with the list using the controller. Movements through the list
+        // will now be one row at a time.
+        mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
+    }
+
+    /**
+     * Returns the position of the given View in the list.
+     *
+     * @param v The View to check for.
+     * @return The position or -1 if the given View is {@code null} or not in the list.
+     */
+    public int positionOf(@Nullable View v) {
+        if (v == null || v.getParent() != mRecyclerView) {
+            return -1;
+        }
+        return mLayoutManager.getPosition(v);
+    }
+
+    private void scroll(int direction) {
+        View focusedView = mRecyclerView.getFocusedChild();
+        if (focusedView != null) {
+            int position = mLayoutManager.getPosition(focusedView);
+            int newPosition =
+                    Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
+            if (newPosition != position) {
+                // newPosition/position are adapter positions.
+                // Convert to layout position by subtracting adapter position of view at layout
+                // position 0.
+                View childAt = mRecyclerView.getChildAt(
+                        newPosition - mLayoutManager.getPosition(mLayoutManager.getChildAt(0)));
+                if (childAt != null) {
+                    childAt.requestFocus();
+                }
+            }
+        }
+    }
+
+    private boolean canScroll(int direction) {
+        View focusedView = mRecyclerView.getFocusedChild();
+        if (focusedView != null) {
+            int position = mLayoutManager.getPosition(focusedView);
+            int newPosition =
+                    Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
+            if (newPosition != position) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @NonNull
+    public CarRecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    /**
+     * Scrolls to the given position in the PagedListView.
+     *
+     * @param position The position in the list to scroll to.
+     */
+    public void scrollToPosition(int position) {
+        mLayoutManager.scrollToPosition(position);
+
+        // Sometimes #scrollToPosition doesn't change the scroll state so we need to make sure
+        // the pagination arrows actually get updated. See b/http://b/15801119
+        mHandler.post(mUpdatePaginationRunnable);
+    }
+
+    /**
+     * Sets the adapter for the list.
+     *
+     * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw an {@link
+     * IllegalArgumentException}.
+     */
+    public void setAdapter(
+            @NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
+        if (!(adapter instanceof ItemCap)) {
+            throw new IllegalArgumentException("ERROR: adapter ["
+                    + adapter.getClass().getCanonicalName() + "] MUST implement ItemCap");
+        }
+
+        mAdapter = adapter;
+        mRecyclerView.setAdapter(adapter);
+        updateMaxItems();
+    }
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @NonNull
+    public CarLayoutManager getLayoutManager() {
+        return mLayoutManager;
+    }
+
+    @Nullable
+    @SuppressWarnings("unchecked")
+    public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> getAdapter() {
+        return mRecyclerView.getAdapter();
+    }
+
+    /**
+     * Sets the maximum number of the pages that can be shown in the PagedListView. The size of a
+     * page is  defined as the number of items that fit completely on the screen at once.
+     *
+     * @param maxPages The maximum number of pages that fit on the screen. Should be positive.
+     */
+    public void setMaxPages(int maxPages) {
+        if (maxPages < 0) {
+            return;
+        }
+        mMaxPages = maxPages;
+        updateMaxItems();
+    }
+
+    /**
+     * Returns the maximum number of pages allowed in the PagedListView. This number is set by
+     * {@link #setMaxPages(int)}. If that method has not been called, then this value should match
+     * the default value.
+     *
+     * @return The maximum number of pages to be shown.
+     */
+    public int getMaxPages() {
+        return mMaxPages;
+    }
+
+    /**
+     * Gets the number of rows per page. Default value of mRowsPerPage is -1. If the first child of
+     * CarLayoutManager is null or the height of the first child is 0, it will return 1.
+     */
+    public int getRowsPerPage() {
+        return mRowsPerPage;
+    }
+
+    /** Resets the maximum number of pages to be shown to be the default. */
+    public void resetMaxPages() {
+        mMaxPages = getDefaultMaxPages();
+        updateMaxItems();
+    }
+
+    /**
+     * @return The position of first visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getFirstFullyVisibleChildPosition() {
+        return mLayoutManager.getFirstFullyVisibleChildPosition();
+    }
+
+    /**
+     * @return The position of last visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getLastFullyVisibleChildPosition() {
+        return mLayoutManager.getLastFullyVisibleChildPosition();
+    }
+
+    /**
+     * Adds an {@link android.support.v7.widget.RecyclerView.ItemDecoration} to this PagedListView.
+     *
+     * @param decor The decoration to add.
+     * @see RecyclerView#addItemDecoration(RecyclerView.ItemDecoration)
+     */
+    public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.addItemDecoration(decor);
+    }
+
+    /**
+     * Removes the given {@link android.support.v7.widget.RecyclerView.ItemDecoration} from this
+     * PagedListView.
+     *
+     * <p>The decoration will function the same as the item decoration for a {@link RecyclerView}.
+     *
+     * @param decor The decoration to remove.
+     * @see RecyclerView#removeItemDecoration(RecyclerView.ItemDecoration)
+     */
+    public void removeItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.removeItemDecoration(decor);
+    }
+
+    /**
+     * Adds an {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} to this
+     * PagedListView.
+     *
+     * <p>The listener will function the same as the listener for a regular {@link RecyclerView}.
+     *
+     * @param touchListener The touch listener to add.
+     * @see RecyclerView#addOnItemTouchListener(RecyclerView.OnItemTouchListener)
+     */
+    public void addOnItemTouchListener(@NonNull RecyclerView.OnItemTouchListener touchListener) {
+        mRecyclerView.addOnItemTouchListener(touchListener);
+    }
+
+    /**
+     * Removes the given {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} from
+     * the PagedListView.
+     *
+     * @param touchListener The touch listener to remove.
+     * @see RecyclerView#removeOnItemTouchListener(RecyclerView.OnItemTouchListener)
+     */
+    public void removeOnItemTouchListener(@NonNull RecyclerView.OnItemTouchListener touchListener) {
+        mRecyclerView.removeOnItemTouchListener(touchListener);
+    }
+    /**
+     * Sets how this {@link PagedListView} responds to day/night configuration changes. By
+     * default, the PagedListView is darker in the day and lighter at night.
+     *
+     * @param dayNightStyle A value from {@link DayNightStyle}.
+     * @see DayNightStyle
+     */
+    public void setDayNightStyle(@DayNightStyle int dayNightStyle) {
+        // Update the scrollbar
+        mScrollBarView.setDayNightStyle(dayNightStyle);
+
+        int decorCount = mRecyclerView.getItemDecorationCount();
+        for (int i = 0; i < decorCount; i++) {
+            RecyclerView.ItemDecoration decor = mRecyclerView.getItemDecorationAt(i);
+            if (decor instanceof DividerDecoration) {
+                ((DividerDecoration) decor).updateDividerColor();
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link android.support.v7.widget.RecyclerView.ViewHolder} that corresponds to the
+     * last child in the PagedListView that is fully visible.
+     *
+     * @return The corresponding ViewHolder or {@code null} if none exists.
+     */
+    @Nullable
+    public RecyclerView.ViewHolder getLastViewHolder() {
+        View lastFullyVisible = mLayoutManager.getLastFullyVisibleChild();
+        if (lastFullyVisible == null) {
+            return null;
+        }
+        int lastFullyVisibleAdapterPosition = mLayoutManager.getPosition(lastFullyVisible);
+        RecyclerView.ViewHolder lastViewHolder = getRecyclerView()
+                .findViewHolderForAdapterPosition(lastFullyVisibleAdapterPosition + 1);
+        // We want to get the very last ViewHolder in the list, even if it's only fully visible
+        // If it doesn't exist, return the last fully visible ViewHolder.
+        if (lastViewHolder == null) {
+            lastViewHolder = getRecyclerView()
+                    .findViewHolderForAdapterPosition(lastFullyVisibleAdapterPosition);
+        }
+        return lastViewHolder;
+    }
+
+    /**
+     * Sets the {@link OnScrollListener} that will be notified of scroll events within the
+     * PagedListView.
+     *
+     * @param listener The scroll listener to set.
+     */
+    public void setOnScrollListener(OnScrollListener listener) {
+        mOnScrollListener = listener;
+        mLayoutManager.setOnScrollListener(mOnScrollListener);
+    }
+
+    /** Returns the page the given position is on, starting with page 0. */
+    public int getPage(int position) {
+        if (mRowsPerPage == -1) {
+            return -1;
+        }
+        if (mRowsPerPage == 0) {
+            return 0;
+        }
+        return position / mRowsPerPage;
+    }
+
+    /**
+     * Sets the default number of pages that this PagedListView is limited to.
+     *
+     * @param newDefault The default number of pages. Should be positive.
+     */
+    public void setDefaultMaxPages(int newDefault) {
+        if (newDefault < 0) {
+            return;
+        }
+        mDefaultMaxPages = newDefault;
+    }
+
+    /** Returns the default number of pages the list should have */
+    protected int getDefaultMaxPages() {
+        // assume list shown in response to a click, so, reduce number of clicks by one
+        return mDefaultMaxPages - 1;
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        // if a late item is added to the top of the layout after the layout is stabilized, causing
+        // the former top item to be pushed to the 2nd page, the focus will still be on the former
+        // top item. Since our car layout manager tries to scroll the viewport so that the focused
+        // item is visible, the view port will be on the 2nd page. That means the newly added item
+        // will not be visible, on the first page.
+
+        // what we want to do is: if the formerly focused item is the first one in the list, any
+        // item added above it will make the focus to move to the new first item.
+        // if the focus is not on the formerly first item, then we don't need to do anything. Let
+        // the layout manager do the job and scroll the viewport so the currently focused item
+        // is visible.
+
+        // we need to calculate whether we want to request focus here, before the super call,
+        // because after the super call, the first born might be changed.
+        View focusedChild = mLayoutManager.getFocusedChild();
+        View firstBorn = mLayoutManager.getChildAt(0);
+
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mAdapter != null) {
+            int itemCount = mAdapter.getItemCount();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, String.format(
+                        "onLayout hasFocus: %s, mLastItemCount: %s, itemCount: %s, "
+                                + "focusedChild: %s, firstBorn: %s, isInTouchMode: %s, "
+                                + "mNeedsFocus: %s",
+                        hasFocus(),
+                        mLastItemCount,
+                        itemCount,
+                        focusedChild,
+                        firstBorn,
+                        isInTouchMode(),
+                        mNeedsFocus));
+            }
+            updateMaxItems();
+            // This is a workaround for missing focus because isInTouchMode() is not always
+            // returning the right value.
+            // This is okay for the Engine release since focus is always showing.
+            // However, in Tala and Fender, we want to show focus only when the user uses
+            // hardware controllers, so we need to revisit this logic. b/22990605.
+            if (mNeedsFocus && itemCount > 0) {
+                if (focusedChild == null) {
+                    requestFocus();
+                }
+                mNeedsFocus = false;
+            }
+            if (itemCount > mLastItemCount && focusedChild == firstBorn) {
+                requestFocus();
+            }
+            mLastItemCount = itemCount;
+        }
+        // We need to update the scroll buttons after layout has happened.
+        // Determining if a scrollbar is necessary requires looking at the layout of the child
+        // views. Therefore, this determination can only be done after layout has happened.
+        // Note: don't animate here to prevent b/26849677
+        updatePaginationButtons(false /*animate*/);
+    }
+
+    /**
+     * Returns the View at the given position within the list.
+     *
+     * @param position A position within the list.
+     * @return The View or {@code null} if no View exists at the given position.
+     */
+    @Nullable
+    public View findViewByPosition(int position) {
+        return mLayoutManager.findViewByPosition(position);
+    }
+
+    /**
+     * Determines if scrollbar should be visible or not and shows/hides it accordingly. If this is
+     * being called as a result of adapter changes, it should be called after the new layout has
+     * been calculated because the method of determining scrollbar visibility uses the current
+     * layout. If this is called after an adapter change but before the new layout, the visibility
+     * determination may not be correct.
+     *
+     * @param animate {@code true} if the scrollbar should animate to its new position.
+     *                {@code false} if no animation is used
+     */
+    protected void updatePaginationButtons(boolean animate) {
+        if (!mScrollBarEnabled) {
+            // Don't change the visibility of the ScrollBar unless it's enabled.
+            return;
+        }
+
+        if ((mLayoutManager.isAtTop() && mLayoutManager.isAtBottom())
+                || mLayoutManager.getItemCount() == 0) {
+            mScrollBarView.setVisibility(View.INVISIBLE);
+        } else {
+            mScrollBarView.setVisibility(View.VISIBLE);
+        }
+        mScrollBarView.setUpEnabled(shouldEnablePageUpButton());
+        mScrollBarView.setDownEnabled(shouldEnablePageDownButton());
+
+        mScrollBarView.setParameters(
+                mRecyclerView.computeVerticalScrollRange(),
+                mRecyclerView.computeVerticalScrollOffset(),
+                mRecyclerView.computeVerticalScrollExtent(),
+                animate);
+        invalidate();
+    }
+
+    protected boolean shouldEnablePageUpButton() {
+        return !mLayoutManager.isAtTop();
+    }
+
+    protected boolean shouldEnablePageDownButton() {
+        return !mLayoutManager.isAtBottom();
+    }
+
+    @UiThread
+    protected void updateMaxItems() {
+        if (mAdapter == null) {
+            return;
+        }
+
+        final int originalCount = mAdapter.getItemCount();
+        updateRowsPerPage();
+        ((ItemCap) mAdapter).setMaxItems(calculateMaxItemCount());
+        final int newCount = mAdapter.getItemCount();
+        if (newCount == originalCount) {
+            return;
+        }
+
+        if (newCount < originalCount) {
+            mAdapter.notifyItemRangeRemoved(newCount, originalCount - newCount);
+        } else {
+            mAdapter.notifyItemRangeInserted(originalCount, newCount - originalCount);
+        }
+    }
+
+    protected int calculateMaxItemCount() {
+        final View firstChild = mLayoutManager.getChildAt(0);
+        if (firstChild == null || firstChild.getHeight() == 0) {
+            return -1;
+        } else {
+            return (mMaxPages < 0) ? -1 : mRowsPerPage * mMaxPages;
+        }
+    }
+
+    /**
+     * Updates the rows number per current page, which is used for calculating how many items we
+     * want to show.
+     */
+    protected void updateRowsPerPage() {
+        final View firstChild = mLayoutManager.getChildAt(0);
+        if (firstChild == null || firstChild.getHeight() == 0) {
+            mRowsPerPage = 1;
+        } else {
+            mRowsPerPage = Math.max(1, (getHeight() - getPaddingTop()) / firstChild.getHeight());
+        }
+    }
+
+    private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                    if (mOnScrollListener != null) {
+                        mOnScrollListener.onScrolled(recyclerView, dx, dy);
+                        if (!mLayoutManager.isAtTop() && mLayoutManager.isAtBottom()) {
+                            mOnScrollListener.onReachBottom();
+                        } else if (mLayoutManager.isAtTop() || !mLayoutManager.isAtBottom()) {
+                            mOnScrollListener.onLeaveBottom();
+                        }
+                    }
+                    updatePaginationButtons(false);
+                }
+
+                @Override
+                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                    if (mOnScrollListener != null) {
+                        mOnScrollListener.onScrollStateChanged(recyclerView, newState);
+                    }
+                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                        mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
+                    }
+                }
+            };
+
+    protected final Runnable mPaginationRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    boolean upPressed = mScrollBarView.isUpPressed();
+                    boolean downPressed = mScrollBarView.isDownPressed();
+                    if (upPressed && downPressed) {
+                        return;
+                    }
+                    if (upPressed) {
+                        mRecyclerView.pageUp();
+                    } else if (downPressed) {
+                        mRecyclerView.pageDown();
+                    }
+                }
+            };
+
+    private final Runnable mUpdatePaginationRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    updatePaginationButtons(true /*animate*/);
+                }
+            };
+
+    /** Used to listen for {@code PagedListView} scroll events. */
+    public abstract static class OnScrollListener {
+        /** Called when menu reaches the bottom */
+        public void onReachBottom() {}
+        /** Called when menu leaves the bottom */
+        public void onLeaveBottom() {}
+        /** Called when scroll up button is clicked */
+        public void onScrollUpButtonClicked() {}
+        /** Called when scroll down button is clicked */
+        public void onScrollDownButtonClicked() {}
+        /** Called when scrolling to the previous page via up gesture */
+        public void onGestureUp() {}
+        /** Called when scrolling to the next page via down gesture */
+        public void onGestureDown() {}
+
+        /**
+         * Called when RecyclerView.OnScrollListener#onScrolled is called. See
+         * RecyclerView.OnScrollListener
+         */
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {}
+
+        /** See RecyclerView.OnScrollListener */
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {}
+
+        /** Called when the view scrolls up a page */
+        public void onPageUp() {}
+
+        /** Called when the view scrolls down a page */
+        public void onPageDown() {}
+    }
+
+    /**
+     * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will draw a dividing
+     * line between each item in the RecyclerView that it is added to.
+     */
+    public static class DividerDecoration extends RecyclerView.ItemDecoration {
+        private final Context mContext;
+        private final Paint mPaint;
+        private final int mDividerHeight;
+        private final int mDividerStartMargin;
+        @IdRes private final int mDividerStartId;
+        @IdRes private final int mDvidierEndId;
+
+        /**
+         * @param dividerStartMargin The start offset of the dividing line. This offset will be
+         *     relative to {@code dividerStartId} if that value is given.
+         * @param dividerStartId A child view id whose starting edge will be used as the starting
+         *     edge of the dividing line. If this value is {@link #INVALID_RESOURCE_ID}, the top
+         *     container of each child view will be used.
+         * @param dividerEndId A child view id whose ending edge will be used as the starting edge
+         *     of the dividing lin.e If this value is {@link #INVALID_RESOURCE_ID}, then the top
+         *     container view of each child will be used.
+         */
+        private DividerDecoration(Context context, int dividerStartMargin,
+                @IdRes int dividerStartId, @IdRes int dividerEndId) {
+            mContext = context;
+            mDividerStartMargin = dividerStartMargin;
+            mDividerStartId = dividerStartId;
+            mDvidierEndId = dividerEndId;
+
+            Resources res = context.getResources();
+            mPaint = new Paint();
+            mPaint.setColor(res.getColor(R.color.car_list_divider));
+            mDividerHeight = res.getDimensionPixelSize(R.dimen.car_divider_height);
+        }
+
+        /** Updates the list divider color which may have changed due to a day night transition. */
+        public void updateDividerColor() {
+            mPaint.setColor(mContext.getResources().getColor(R.color.car_list_divider));
+        }
+
+        @Override
+        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+            for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++) {
+                View container = parent.getChildAt(i);
+                View startChild =
+                        mDividerStartId != INVALID_RESOURCE_ID
+                                ? container.findViewById(mDividerStartId)
+                                : container;
+
+                View endChild =
+                        mDvidierEndId != INVALID_RESOURCE_ID
+                                ? container.findViewById(mDvidierEndId)
+                                : container;
+
+                if (startChild == null || endChild == null) {
+                    continue;
+                }
+
+                int left = mDividerStartMargin + startChild.getLeft();
+                int right = endChild.getRight();
+                int bottom = container.getBottom();
+                int top = bottom - mDividerHeight;
+
+                // Draw a divider line between each item. No need to draw the line for the last
+                // item.
+                if (i != childCount - 1) {
+                    c.drawRect(left, top, right, bottom, mPaint);
+                }
+            }
+        }
+    }
+}
diff --git a/android/support/car/widget/PagedScrollBarView.java b/android/support/car/widget/PagedScrollBarView.java
new file mode 100644
index 0000000..125b354
--- /dev/null
+++ b/android/support/car/widget/PagedScrollBarView.java
@@ -0,0 +1,253 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.support.car.R;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/** A custom view to provide list scroll behaviour -- up/down buttons and scroll indicator. */
+public class PagedScrollBarView extends FrameLayout
+        implements View.OnClickListener, View.OnLongClickListener {
+    private static final float BUTTON_DISABLED_ALPHA = 0.2f;
+
+    @DayNightStyle private int mDayNightStyle;
+
+    /** Listener for when the list should paginate. */
+    public interface PaginationListener {
+        int PAGE_UP = 0;
+        int PAGE_DOWN = 1;
+
+        /** Called when the linked view should be paged in the given direction */
+        void onPaginate(int direction);
+    }
+
+    private final ImageView mUpButton;
+    private final ImageView mDownButton;
+    private final ImageView mScrollThumb;
+    /** The "filler" view between the up and down buttons */
+    private final View mFiller;
+
+    private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
+    private final int mMinThumbLength;
+    private final int mMaxThumbLength;
+    private PaginationListener mPaginationListener;
+
+    public PagedScrollBarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+    }
+
+    public PagedScrollBarView(Context context, AttributeSet attrs, int defStyleAttrs) {
+        this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+    }
+
+    public PagedScrollBarView(
+            Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+        super(context, attrs, defStyleAttrs, defStyleRes);
+
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.car_paged_scrollbar_buttons, this /* root */,
+                true /* attachToRoot */);
+
+        mUpButton = (ImageView) findViewById(R.id.page_up);
+        mUpButton.setOnClickListener(this);
+        mUpButton.setOnLongClickListener(this);
+        mDownButton = (ImageView) findViewById(R.id.page_down);
+        mDownButton.setOnClickListener(this);
+        mDownButton.setOnLongClickListener(this);
+
+        mScrollThumb = (ImageView) findViewById(R.id.scrollbar_thumb);
+        mFiller = findViewById(R.id.filler);
+
+        mMinThumbLength = getResources().getDimensionPixelSize(R.dimen.min_thumb_height);
+        mMaxThumbLength = getResources().getDimensionPixelSize(R.dimen.max_thumb_height);
+    }
+
+    @Override
+    public void onClick(View v) {
+        dispatchPageClick(v);
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        dispatchPageClick(v);
+        return true;
+    }
+
+    /**
+     * Sets the listener that will be notified when the up and down buttons have been pressed.
+     *
+     * @param listener The listener to set.
+     */
+    public void setPaginationListener(PaginationListener listener) {
+        mPaginationListener = listener;
+    }
+
+    /** Returns {@code true} if the "up" button is pressed */
+    public boolean isUpPressed() {
+        return mUpButton.isPressed();
+    }
+
+    /** Returns {@code true} if the "down" button is pressed */
+    public boolean isDownPressed() {
+        return mDownButton.isPressed();
+    }
+
+    /** Sets the range, offset and extent of the scroll bar. See {@link View}. */
+    public void setParameters(int range, int offset, int extent, boolean animate) {
+        // This method is where we take the computed parameters from the CarLayoutManager and
+        // render it within the specified constraints ({@link #mMaxThumbLength} and
+        // {@link #mMinThumbLength}).
+        final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
+
+        int thumbLength = extent * size / range;
+        thumbLength = Math.max(Math.min(thumbLength, mMaxThumbLength), mMinThumbLength);
+
+        int thumbOffset = size - thumbLength;
+        if (isDownEnabled()) {
+            // We need to adjust the offset so that it fits into the possible space inside the
+            // filler with regarding to the constraints set by mMaxThumbLength and mMinThumbLength.
+            thumbOffset = (size - thumbLength) * offset / range;
+        }
+
+        // Sets the size of the thumb and request a redraw if needed.
+        final ViewGroup.LayoutParams lp = mScrollThumb.getLayoutParams();
+        if (lp.height != thumbLength) {
+            lp.height = thumbLength;
+            mScrollThumb.requestLayout();
+        }
+
+        moveY(mScrollThumb, thumbOffset, animate);
+    }
+
+    /**
+     * Sets how this {@link PagedScrollBarView} responds to day/night configuration changes. By
+     * default, the PagedScrollBarView is darker in the day and lighter at night.
+     *
+     * @param dayNightStyle A value from {@link DayNightStyle}.
+     * @see DayNightStyle
+     */
+    public void setDayNightStyle(@DayNightStyle int dayNightStyle) {
+        mDayNightStyle = dayNightStyle;
+        reloadColors();
+    }
+
+    /**
+     * Sets whether or not the up button on the scroll bar is clickable.
+     *
+     * @param enabled {@code true} if the up button is enabled.
+     */
+    public void setUpEnabled(boolean enabled) {
+        mUpButton.setEnabled(enabled);
+        mUpButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+    }
+
+    /**
+     * Sets whether or not the down button on the scroll bar is clickable.
+     *
+     * @param enabled {@code true} if the down button is enabled.
+     */
+    public void setDownEnabled(boolean enabled) {
+        mDownButton.setEnabled(enabled);
+        mDownButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+    }
+
+    /**
+     * Returns whether or not the down button on the scroll bar is clickable.
+     *
+     * @return {@code true} if the down button is enabled. {@code false} otherwise.
+     */
+    public boolean isDownEnabled() {
+        return mDownButton.isEnabled();
+    }
+
+    /** Reload the colors for the current {@link DayNightStyle}. */
+    private void reloadColors() {
+        int tint;
+        int thumbBackground;
+        int upDownBackgroundResId;
+
+        switch (mDayNightStyle) {
+            case DayNightStyle.AUTO:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb);
+                upDownBackgroundResId = R.drawable.car_pagination_background;
+                break;
+            case DayNightStyle.AUTO_INVERSE:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint_inverse);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb_inverse);
+                upDownBackgroundResId = R.drawable.car_pagination_background_inverse;
+                break;
+            case DayNightStyle.FORCE_NIGHT:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint_light);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb_light);
+                upDownBackgroundResId = R.drawable.car_pagination_background_night;
+                break;
+            case DayNightStyle.FORCE_DAY:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint_dark);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb_dark);
+                upDownBackgroundResId = R.drawable.car_pagination_background_day;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown DayNightStyle: " + mDayNightStyle);
+        }
+
+        mScrollThumb.setBackgroundColor(thumbBackground);
+
+        mUpButton.setColorFilter(tint, PorterDuff.Mode.SRC_IN);
+        mUpButton.setBackgroundResource(upDownBackgroundResId);
+
+        mDownButton.setColorFilter(tint, PorterDuff.Mode.SRC_IN);
+        mDownButton.setBackgroundResource(upDownBackgroundResId);
+    }
+
+    private void dispatchPageClick(View v) {
+        final PaginationListener listener = mPaginationListener;
+        if (listener == null) {
+            return;
+        }
+
+        int direction = v.getId() == R.id.page_up
+                ? PaginationListener.PAGE_UP
+                : PaginationListener.PAGE_DOWN;
+        listener.onPaginate(direction);
+    }
+
+    /** Moves the given view to the specified 'y' position. */
+    private void moveY(final View view, float newPosition, boolean animate) {
+        final int duration = animate ? 200 : 0;
+        view.animate()
+                .y(newPosition)
+                .setDuration(duration)
+                .setInterpolator(mPaginationInterpolator)
+                .start();
+    }
+}
diff --git a/android/support/customtabs/CustomTabsCallback.java b/android/support/customtabs/CustomTabsCallback.java
index 818118a..f8d349a 100644
--- a/android/support/customtabs/CustomTabsCallback.java
+++ b/android/support/customtabs/CustomTabsCallback.java
@@ -16,7 +16,9 @@
 
 package android.support.customtabs;
 
+import android.net.Uri;
 import android.os.Bundle;
+import android.support.customtabs.CustomTabsService.Relation;
 
 /**
  * A callback class for custom tabs client to get messages regarding events in their custom tabs. In
@@ -98,4 +100,18 @@
      * @param extras Reserved for future use.
      */
     public void onPostMessage(String message, Bundle extras) {}
+
+    /**
+     * Called when a relationship validation result is available.
+     *
+     * @param relation Relation for which the result is available. Value previously passed to
+     *                 {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. Must be one
+     *                 of the {@code CustomTabsService#RELATION_* } constants.
+     * @param requestedOrigin Origin requested. Value previously passed to
+     *                        {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}.
+     * @param result Whether the relation was validated.
+     * @param extras Reserved for future use.
+     */
+    public void onRelationshipValidationResult(@Relation int relation, Uri requestedOrigin,
+                                               boolean result, Bundle extras) {}
 }
diff --git a/android/support/customtabs/CustomTabsClient.java b/android/support/customtabs/CustomTabsClient.java
index 09f3110..2e955cb 100644
--- a/android/support/customtabs/CustomTabsClient.java
+++ b/android/support/customtabs/CustomTabsClient.java
@@ -31,6 +31,7 @@
 import android.os.RemoteException;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.customtabs.CustomTabsService.Relation;
 import android.text.TextUtils;
 
 import java.util.ArrayList;
@@ -234,6 +235,20 @@
                     }
                 });
             }
+
+            @Override
+            public void onRelationshipValidationResult(
+                    final @Relation int relation, final Uri requestedOrigin, final boolean result,
+                    final @Nullable Bundle extras) throws RemoteException {
+                if (callback == null) return;
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onRelationshipValidationResult(
+                                relation, requestedOrigin, result, extras);
+                    }
+                });
+            }
         };
 
         try {
diff --git a/android/support/customtabs/CustomTabsService.java b/android/support/customtabs/CustomTabsService.java
index 5a940cf..aad174c 100644
--- a/android/support/customtabs/CustomTabsService.java
+++ b/android/support/customtabs/CustomTabsService.java
@@ -78,6 +78,23 @@
      */
     public static final int RESULT_FAILURE_MESSAGING_ERROR = -3;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({RELATION_USE_AS_ORIGIN, RELATION_HANDLE_ALL_URLS})
+    public @interface Relation {
+    }
+
+    /**
+     * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. For
+     * App -> Web transitions, requests the app to use the declared origin to be used as origin for
+     * the client app in the web APIs context.
+     */
+    public static final int RELATION_USE_AS_ORIGIN = 1;
+    /**
+     * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. Requests the
+     * ability to handle all URLs from a given origin.
+     */
+    public static final int RELATION_HANDLE_ALL_URLS = 2;
+
     private final Map<IBinder, DeathRecipient> mDeathRecipientMap = new ArrayMap<>();
 
     private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() {
@@ -137,6 +154,13 @@
             return CustomTabsService.this.postMessage(
                     new CustomTabsSessionToken(callback), message, extras);
         }
+
+        @Override
+        public boolean validateRelationship(
+                ICustomTabsCallback callback, @Relation int relation, Uri origin, Bundle extras) {
+            return CustomTabsService.this.validateRelationship(
+                    new CustomTabsSessionToken(callback), relation, origin, extras);
+        }
     };
 
     @Override
@@ -268,4 +292,23 @@
     @Result
     protected abstract int postMessage(
             CustomTabsSessionToken sessionToken, String message, Bundle extras);
+
+    /**
+     * Request to validate a relationship between the application and an origin.
+     *
+     * If this method returns true, the validation result will be provided through
+     * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}.
+     * Otherwise the request didn't succeed. The client must call
+     * {@link CustomTabsClient#warmup(long)} before this.
+     *
+     * @param sessionToken The unique identifier for the session. Can not be null.
+     * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
+     *                 constants.
+     * @param origin Origin for the relation query.
+     * @param extras Reserved for future use.
+     * @return true if the request has been submitted successfully.
+     */
+    protected abstract boolean validateRelationship(
+            CustomTabsSessionToken sessionToken, @Relation int relation, Uri origin,
+            Bundle extras);
 }
diff --git a/android/support/customtabs/CustomTabsSession.java b/android/support/customtabs/CustomTabsSession.java
index cad897c..a84d63c 100644
--- a/android/support/customtabs/CustomTabsSession.java
+++ b/android/support/customtabs/CustomTabsSession.java
@@ -25,6 +25,8 @@
 import android.os.RemoteException;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.customtabs.CustomTabsService.Relation;
 import android.support.customtabs.CustomTabsService.Result;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -42,6 +44,21 @@
     private final ICustomTabsCallback mCallback;
     private final ComponentName mComponentName;
 
+    /**
+     * Provides browsers a way to generate a mock {@link CustomTabsSession} for testing
+     * purposes.
+     *
+     * @param componentName The component the session should be created for.
+     * @return A mock session with no functionality.
+     */
+    @VisibleForTesting
+    @NonNull
+    public static CustomTabsSession createMockSessionForTesting(
+            @NonNull ComponentName componentName) {
+        return new CustomTabsSession(
+                null, new CustomTabsSessionToken.MockCallback(), componentName);
+    }
+
     /* package */ CustomTabsSession(
             ICustomTabsService service, ICustomTabsCallback callback, ComponentName componentName) {
         mService = service;
@@ -185,6 +202,39 @@
         }
     }
 
+    /**
+     * Requests to validate a relationship between the application and an origin.
+     *
+     * <p>
+     * See <a href="https://developers.google.com/digital-asset-links/v1/getting-started">here</a>
+     * for documentation about Digital Asset Links. This methods requests the browser to verify
+     * a relation with the calling application, to grant the associated rights.
+     *
+     * <p>
+     * If this method returns {@code true}, the validation result will be provided through
+     * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}.
+     * Otherwise the request didn't succeed. The client must call
+     * {@link CustomTabsClient#warmup(long)} before this.
+     *
+     * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
+     *                 constants.
+     * @param origin Origin.
+     * @param extras Reserved for future use.
+     * @return {@code true} if the request has been submitted successfully.
+     */
+    public boolean validateRelationship(@Relation int relation, @NonNull Uri origin,
+                                        @Nullable Bundle extras) {
+        if (relation < CustomTabsService.RELATION_USE_AS_ORIGIN
+                || relation > CustomTabsService.RELATION_HANDLE_ALL_URLS) {
+            return false;
+        }
+        try {
+            return mService.validateRelationship(mCallback, relation, origin, extras);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     /* package */ IBinder getBinder() {
         return mCallback.asBinder();
     }
diff --git a/android/support/customtabs/CustomTabsSessionToken.java b/android/support/customtabs/CustomTabsSessionToken.java
index adfadd9..5a9e1b6 100644
--- a/android/support/customtabs/CustomTabsSessionToken.java
+++ b/android/support/customtabs/CustomTabsSessionToken.java
@@ -17,9 +17,12 @@
 package android.support.customtabs;
 
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.customtabs.CustomTabsService.Relation;
 import android.support.v4.app.BundleCompat;
 import android.util.Log;
 
@@ -32,6 +35,29 @@
     private final ICustomTabsCallback mCallbackBinder;
     private final CustomTabsCallback mCallback;
 
+    /* package */ static class MockCallback extends ICustomTabsCallback.Stub {
+        @Override
+        public void onNavigationEvent(int navigationEvent, Bundle extras) {}
+
+        @Override
+        public void extraCallback(String callbackName, Bundle args) {}
+
+        @Override
+        public void onMessageChannelReady(Bundle extras) {}
+
+        @Override
+        public void onPostMessage(String message, Bundle extras) {}
+
+        @Override
+        public void onRelationshipValidationResult(@Relation int relation, Uri requestedOrigin,
+                boolean result, Bundle extras) {}
+
+        @Override
+        public IBinder asBinder() {
+            return this;
+        }
+    }
+
     /**
      * Obtain a {@link CustomTabsSessionToken} from an intent. See {@link CustomTabsIntent.Builder}
      * for ways to generate an intent for custom tabs.
@@ -46,6 +72,17 @@
         return new CustomTabsSessionToken(ICustomTabsCallback.Stub.asInterface(binder));
     }
 
+    /**
+     * Provides browsers a way to generate a mock {@link CustomTabsSessionToken} for testing
+     * purposes.
+     *
+     * @return A mock token with no functionality.
+     */
+    @NonNull
+    public static CustomTabsSessionToken createMockSessionTokenForTesting() {
+        return new CustomTabsSessionToken(new MockCallback());
+    }
+
     CustomTabsSessionToken(ICustomTabsCallback callbackBinder) {
         mCallbackBinder = callbackBinder;
         mCallback = new CustomTabsCallback() {
@@ -85,6 +122,18 @@
                     Log.e(TAG, "RemoteException during ICustomTabsCallback transaction");
                 }
             }
+
+            @Override
+            public void onRelationshipValidationResult(@Relation int relation, Uri origin,
+                                                       boolean result, Bundle extras) {
+                try {
+                    mCallbackBinder.onRelationshipValidationResult(
+                            relation, origin, result, extras);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException during ICustomTabsCallback transaction");
+                }
+            }
+
         };
     }
 
diff --git a/android/support/customtabs/TrustedWebUtils.java b/android/support/customtabs/TrustedWebUtils.java
new file mode 100644
index 0000000..e9a2233
--- /dev/null
+++ b/android/support/customtabs/TrustedWebUtils.java
@@ -0,0 +1,82 @@
+/*
+ * 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.customtabs;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.BundleCompat;
+
+/**
+ * Class for utilities and convenience calls for opening a qualifying web page as a
+ * Trusted Web Activity.
+ *
+ * Trusted Web Activity is a fullscreen UI with no visible browser controls that hosts web pages
+ * meeting certain criteria. The full list of qualifications is at the implementing browser's
+ * discretion, but minimum recommended set is for the web page :
+ *  <ul>
+ *      <li>To have declared delegate_permission/common.handle_all_urls relationship with the
+ *      launching client application ensuring 1:1 trust between the Android native and web
+ *      components. See https://developers.google.com/digital-asset-links/ for details.</li>
+ *      <li>To work as a reliable, fast and engaging standalone component within the launching app's
+ *      flow.</li>
+ *      <li>To be accessible and operable even when offline.</li>
+ *  </ul>
+ *
+ *  Fallback behaviors may also differ with implementation. Possibilities are launching the page in
+ *  a custom tab, or showing it in browser UI. Browsers are encouraged to use
+ *  {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}
+ *  for sending details of the verification results.
+ */
+public class TrustedWebUtils {
+
+    /**
+     * Boolean extra that triggers a {@link CustomTabsIntent} launch to be in a fullscreen UI with
+     * no browser controls.
+     *
+     * @see TrustedWebUtils#launchAsTrustedWebActivity(Context, CustomTabsIntent, Uri).
+     */
+    public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY =
+            "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+
+    private TrustedWebUtils() {}
+
+    /**
+     * Launch the given {@link CustomTabsIntent} as a Trusted Web Activity. The given
+     * {@link CustomTabsIntent} should have a valid {@link CustomTabsSession} associated with it
+     * during construction. Once the Trusted Web Activity is launched, browser side implementations
+     * may have their own fallback behavior (e.g. Showing the page in a custom tab UI with toolbar)
+     * based on qualifications listed above or more.
+     *
+     * @param context {@link Context} to use while launching the {@link CustomTabsIntent}.
+     * @param customTabsIntent The {@link CustomTabsIntent} to use for launching the
+     *                         Trusted Web Activity. Note that all customizations in the given
+     *                         associated with browser toolbar controls will be ignored.
+     * @param uri The web page to launch as Trusted Web Activity.
+     */
+    public static void launchAsTrustedWebActivity(@NonNull Context context,
+            @NonNull CustomTabsIntent customTabsIntent, @NonNull Uri uri) {
+        if (BundleCompat.getBinder(
+                customTabsIntent.intent.getExtras(), CustomTabsIntent.EXTRA_SESSION) == null) {
+            throw new IllegalArgumentException(
+                    "Given CustomTabsIntent should be associated with a valid CustomTabsSession");
+        }
+        customTabsIntent.intent.putExtra(EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
+        customTabsIntent.launchUrl(context, uri);
+    }
+}
diff --git a/android/support/media/ExifInterface.java b/android/support/media/ExifInterface.java
index b790cd2..72b61cb 100644
--- a/android/support/media/ExifInterface.java
+++ b/android/support/media/ExifInterface.java
@@ -22,6 +22,7 @@
 import android.location.Location;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 import android.util.Pair;
 
@@ -3699,7 +3700,7 @@
     /**
      * Reads Exif tags from the specified image file.
      */
-    public ExifInterface(String filename) throws IOException {
+    public ExifInterface(@NonNull String filename) throws IOException {
         if (filename == null) {
             throw new IllegalArgumentException("filename cannot be null");
         }
@@ -3720,7 +3721,7 @@
      * should close the input stream after use. This constructor is not intended to be used with
      * an input stream that performs any networking operations.
      */
-    public ExifInterface(InputStream inputStream) throws IOException {
+    public ExifInterface(@NonNull InputStream inputStream) throws IOException {
         if (inputStream == null) {
             throw new IllegalArgumentException("inputStream cannot be null");
         }
@@ -3739,7 +3740,8 @@
      *
      * @param tag the name of the tag.
      */
-    private ExifAttribute getExifAttribute(String tag) {
+    @Nullable
+    private ExifAttribute getExifAttribute(@NonNull String tag) {
         if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
             if (DEBUG) {
                 Log.d(TAG, "getExifAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
@@ -3764,7 +3766,8 @@
      *
      * @param tag the name of the tag.
      */
-    public String getAttribute(String tag) {
+    @Nullable
+    public String getAttribute(@NonNull String tag) {
         ExifAttribute attribute = getExifAttribute(tag);
         if (attribute != null) {
             if (!sTagSetForCompatibility.contains(tag)) {
@@ -3804,7 +3807,7 @@
      * @param tag the name of the tag.
      * @param defaultValue the value to return if the tag is not available.
      */
-    public int getAttributeInt(String tag, int defaultValue) {
+    public int getAttributeInt(@NonNull String tag, int defaultValue) {
         ExifAttribute exifAttribute = getExifAttribute(tag);
         if (exifAttribute == null) {
             return defaultValue;
@@ -3825,7 +3828,7 @@
      * @param tag the name of the tag.
      * @param defaultValue the value to return if the tag is not available.
      */
-    public double getAttributeDouble(String tag, double defaultValue) {
+    public double getAttributeDouble(@NonNull String tag, double defaultValue) {
         ExifAttribute exifAttribute = getExifAttribute(tag);
         if (exifAttribute == null) {
             return defaultValue;
@@ -3844,7 +3847,7 @@
      * @param tag the name of the tag.
      * @param value the value of the tag.
      */
-    public void setAttribute(String tag, String value) {
+    public void setAttribute(@NonNull String tag, @Nullable String value) {
         if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
             if (DEBUG) {
                 Log.d(TAG, "setAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
@@ -4320,6 +4323,7 @@
      * The returned data can be decoded using
      * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
      */
+    @Nullable
     public byte[] getThumbnail() {
         if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
             return getThumbnailBytes();
@@ -4331,6 +4335,7 @@
      * Returns the thumbnail bytes inside the image file, regardless of the compression type of the
      * thumbnail image.
      */
+    @Nullable
     public byte[] getThumbnailBytes() {
         if (!mHasThumbnail) {
             return null;
@@ -4379,6 +4384,7 @@
      * Creates and returns a Bitmap object of the thumbnail image based on the byte array and the
      * thumbnail compression value, or {@code null} if the compression type is unsupported.
      */
+    @Nullable
     public Bitmap getThumbnailBitmap() {
         if (!mHasThumbnail) {
             return null;
@@ -4425,6 +4431,7 @@
      * @return two-element array, the offset in the first value, and length in
      *         the second, or {@code null} if no thumbnail was found.
      */
+    @Nullable
     public long[] getThumbnailRange() {
         if (!mHasThumbnail) {
             return null;
@@ -4462,6 +4469,7 @@
      * array where the first element is the latitude and the second element is the longitude.
      * Otherwise, it returns null.
      */
+    @Nullable
     public double[] getLatLong() {
         String latValue = getAttribute(TAG_GPS_LATITUDE);
         String latRef = getAttribute(TAG_GPS_LATITUDE_REF);
diff --git a/android/support/percent/PercentFrameLayout.java b/android/support/percent/PercentFrameLayout.java
index b9abd39..4190858 100644
--- a/android/support/percent/PercentFrameLayout.java
+++ b/android/support/percent/PercentFrameLayout.java
@@ -126,6 +126,7 @@
  *         app:layout_constraintBottom_toBottomOf="@+id/bottom_guideline" /&gt
  *
  * &lt;/android.support.constraint.ConstraintLayout&gt
+ * </pre>
  */
 @Deprecated
 public class PercentFrameLayout extends FrameLayout {
diff --git a/android/support/testutils/AppCompatActivityUtils.java b/android/support/testutils/AppCompatActivityUtils.java
new file mode 100644
index 0000000..49ccc1b
--- /dev/null
+++ b/android/support/testutils/AppCompatActivityUtils.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 android.support.testutils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for testing AppCompat activities.
+ */
+public class AppCompatActivityUtils {
+    private static final Runnable DO_NOTHING = new Runnable() {
+        @Override
+        public void run() {
+        }
+    };
+
+    /**
+     * Waits for the execution of the provided activity test rule.
+     *
+     * @param rule Activity test rule to wait for.
+     */
+    public static void waitForExecution(
+            final ActivityTestRule<? extends RecreatedAppCompatActivity> rule) {
+        // Wait for two cycles. When starting a postponed transition, it will post to
+        // the UI thread and then the execution will be added onto the queue after that.
+        // The two-cycle wait makes sure fragments have the opportunity to complete both
+        // before returning.
+        try {
+            rule.runOnUiThread(DO_NOTHING);
+            rule.runOnUiThread(DO_NOTHING);
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    private static void runOnUiThreadRethrow(
+            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, Runnable r) {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            r.run();
+        } else {
+            try {
+                rule.runOnUiThread(r);
+            } catch (Throwable t) {
+                throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Restarts the RecreatedAppCompatActivity and waits for the new activity to be resumed.
+     *
+     * @return The newly-restarted RecreatedAppCompatActivity
+     */
+    public static <T extends RecreatedAppCompatActivity> T recreateActivity(
+            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, final T activity)
+            throws InterruptedException {
+        // Now switch the orientation
+        RecreatedAppCompatActivity.sResumed = new CountDownLatch(1);
+        RecreatedAppCompatActivity.sDestroyed = new CountDownLatch(1);
+
+        runOnUiThreadRethrow(rule, new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+        assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS));
+        assertTrue(RecreatedAppCompatActivity.sDestroyed.await(1, TimeUnit.SECONDS));
+        T newActivity = (T) RecreatedAppCompatActivity.sActivity;
+
+        waitForExecution(rule);
+
+        RecreatedAppCompatActivity.clearState();
+        return newActivity;
+    }
+}
diff --git a/android/support/testutils/FragmentActivityUtils.java b/android/support/testutils/FragmentActivityUtils.java
new file mode 100644
index 0000000..7d12deb
--- /dev/null
+++ b/android/support/testutils/FragmentActivityUtils.java
@@ -0,0 +1,91 @@
+/*
+ * 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.testutils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for testing fragment activities.
+ */
+public class FragmentActivityUtils {
+    private static final Runnable DO_NOTHING = new Runnable() {
+        @Override
+        public void run() {
+        }
+    };
+
+    private static void waitForExecution(final ActivityTestRule<? extends FragmentActivity> rule) {
+        // Wait for two cycles. When starting a postponed transition, it will post to
+        // the UI thread and then the execution will be added onto the queue after that.
+        // The two-cycle wait makes sure fragments have the opportunity to complete both
+        // before returning.
+        try {
+            rule.runOnUiThread(DO_NOTHING);
+            rule.runOnUiThread(DO_NOTHING);
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    private static void runOnUiThreadRethrow(ActivityTestRule<? extends Activity> rule,
+            Runnable r) {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            r.run();
+        } else {
+            try {
+                rule.runOnUiThread(r);
+            } catch (Throwable t) {
+                throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Restarts the RecreatedActivity and waits for the new activity to be resumed.
+     *
+     * @return The newly-restarted Activity
+     */
+    public static <T extends RecreatedActivity> T recreateActivity(
+            ActivityTestRule<? extends RecreatedActivity> rule, final T activity)
+            throws InterruptedException {
+        // Now switch the orientation
+        RecreatedActivity.sResumed = new CountDownLatch(1);
+        RecreatedActivity.sDestroyed = new CountDownLatch(1);
+
+        runOnUiThreadRethrow(rule, new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+        assertTrue(RecreatedActivity.sResumed.await(1, TimeUnit.SECONDS));
+        assertTrue(RecreatedActivity.sDestroyed.await(1, TimeUnit.SECONDS));
+        T newActivity = (T) RecreatedActivity.sActivity;
+
+        waitForExecution(rule);
+
+        RecreatedActivity.clearState();
+        return newActivity;
+    }
+}
diff --git a/android/support/testutils/RecreatedActivity.java b/android/support/testutils/RecreatedActivity.java
new file mode 100644
index 0000000..aaea3a9
--- /dev/null
+++ b/android/support/testutils/RecreatedActivity.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.support.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Extension of {@link FragmentActivity} that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * {@link FragmentActivityUtils#recreateActivity(ActivityTestRule, RecreatedActivity)} API.
+ */
+public class RecreatedActivity extends FragmentActivity {
+    // These must be cleared after each test using clearState()
+    public static RecreatedActivity sActivity;
+    public static CountDownLatch sResumed;
+    public static CountDownLatch sDestroyed;
+
+    static void clearState() {
+        sActivity = null;
+        sResumed = null;
+        sDestroyed = null;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sActivity = this;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sResumed != null) {
+            sResumed.countDown();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (sDestroyed != null) {
+            sDestroyed.countDown();
+        }
+    }
+}
diff --git a/android/support/testutils/RecreatedAppCompatActivity.java b/android/support/testutils/RecreatedAppCompatActivity.java
new file mode 100644
index 0000000..d5645a3
--- /dev/null
+++ b/android/support/testutils/RecreatedAppCompatActivity.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 android.support.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Extension of {@link AppCompatActivity} that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * {@link AppCompatActivityUtils#recreateActivity(ActivityTestRule, RecreatedAppCompatActivity)}
+ * API.
+ */
+public class RecreatedAppCompatActivity extends AppCompatActivity {
+    // These must be cleared after each test using clearState()
+    public static RecreatedAppCompatActivity sActivity;
+    public static CountDownLatch sResumed;
+    public static CountDownLatch sDestroyed;
+
+    static void clearState() {
+        sActivity = null;
+        sResumed = null;
+        sDestroyed = null;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sActivity = this;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sResumed != null) {
+            sResumed.countDown();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (sDestroyed != null) {
+            sDestroyed.countDown();
+        }
+    }
+}
diff --git a/android/support/text/emoji/widget/EmojiEditTextHelper.java b/android/support/text/emoji/widget/EmojiEditTextHelper.java
index edc511f..a999e34 100644
--- a/android/support/text/emoji/widget/EmojiEditTextHelper.java
+++ b/android/support/text/emoji/widget/EmojiEditTextHelper.java
@@ -20,6 +20,7 @@
 import android.os.Build;
 import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.text.emoji.EmojiCompat;
@@ -130,7 +131,8 @@
     /**
      * Updates the InputConnection with emoji support. Should be called from {@link
      * TextView#onCreateInputConnection(EditorInfo)}. When used on devices running API 18 or below,
-     * this method returns {@code inputConnection} that is given as a parameter.
+     * this method returns {@code inputConnection} that is given as a parameter. If
+     * {@code inputConnection} is {@code null}, returns {@code null}.
      *
      * @param inputConnection InputConnection instance created by TextView
      * @param outAttrs        EditorInfo passed into
@@ -138,10 +140,10 @@
      *
      * @return a new InputConnection instance that wraps {@code inputConnection}
      */
-    @NonNull
-    public InputConnection onCreateInputConnection(@NonNull final InputConnection inputConnection,
+    @Nullable
+    public InputConnection onCreateInputConnection(@Nullable final InputConnection inputConnection,
             @NonNull final EditorInfo outAttrs) {
-        Preconditions.checkNotNull(inputConnection, "inputConnection cannot be null");
+        if (inputConnection == null) return null;
         return mHelper.onCreateInputConnection(inputConnection, outAttrs);
     }
 
diff --git a/android/support/v13/app/FragmentCompat.java b/android/support/v13/app/FragmentCompat.java
index 4f21052..31c2343 100644
--- a/android/support/v13/app/FragmentCompat.java
+++ b/android/support/v13/app/FragmentCompat.java
@@ -24,6 +24,7 @@
 import android.os.Looper;
 import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
 
 import java.util.Arrays;
 
@@ -37,6 +38,38 @@
         boolean shouldShowRequestPermissionRationale(Fragment fragment, String permission);
     }
 
+    /**
+     * Customizable delegate that allows delegating permission related compatibility methods
+     * to a custom implementation.
+     *
+     * <p>
+     *     To delegate fragment compatibility methods to a custom class, implement this interface,
+     *     and call {@code FragmentCompat.setPermissionCompatDelegate(delegate);}. All future calls
+     *     to the compatibility methods in this class will first check whether the delegate can
+     *     handle the method call, and invoke the corresponding method if it can.
+     * </p>
+     */
+    public interface PermissionCompatDelegate {
+
+        /**
+         * Determines whether the delegate should handle
+         * {@link FragmentCompat#requestPermissions(Fragment, String[], int)}, and request
+         * permissions if applicable. If this method returns true, it means that permission
+         * request is successfully handled by the delegate, and platform should not perform any
+         * further requests for permission.
+         *
+         * @param fragment The target fragment.
+         * @param permissions The requested permissions.
+         * @param requestCode Application specific request code to match with a result
+         *    reported to {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult(
+         *    int, String[], int[])}.
+         *
+         * @return Whether the delegate has handled the permission request.
+         * @see FragmentCompat#requestPermissions(Fragment, String[], int)
+         */
+        boolean requestPermissions(Fragment fragment, String[] permissions, int requestCode);
+    }
+
     static class FragmentCompatBaseImpl implements FragmentCompatImpl {
         @Override
         public void setUserVisibleHint(Fragment f, boolean deferStart) {
@@ -117,6 +150,26 @@
         }
     }
 
+    private static PermissionCompatDelegate sDelegate;
+
+    /**
+     * Sets the permission delegate for {@code FragmentCompat}. Replaces the previously set
+     * delegate.
+     *
+     * @param delegate The delegate to be set. {@code null} to clear the set delegate.
+     */
+    public static void setPermissionCompatDelegate(PermissionCompatDelegate delegate) {
+        sDelegate = delegate;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static PermissionCompatDelegate getPermissionCompatDelegate() {
+        return sDelegate;
+    }
+
     /**
      * This interface is the contract for receiving the results for permission requests.
      */
@@ -212,6 +265,11 @@
      */
     public static void requestPermissions(@NonNull Fragment fragment,
             @NonNull String[] permissions, int requestCode) {
+        if (sDelegate != null && sDelegate.requestPermissions(fragment, permissions, requestCode)) {
+            // Delegate has handled the request.
+            return;
+        }
+
         IMPL.requestPermissions(fragment, permissions, requestCode);
     }
 
diff --git a/android/support/v13/app/FragmentPagerAdapter.java b/android/support/v13/app/FragmentPagerAdapter.java
index 082f883..e0b788a 100644
--- a/android/support/v13/app/FragmentPagerAdapter.java
+++ b/android/support/v13/app/FragmentPagerAdapter.java
@@ -48,18 +48,18 @@
  * <p>Here is an example implementation of a pager containing fragments of
  * lists:
  *
- * {@sample frameworks/support/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentPagerSupport.java
+ * {@sample frameworks/support/samples/Support13Demos/src/main/java/com/example/android/supportv13/app/FragmentPagerSupport.java
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
  *
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager.xml
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager_list</code> resource containing each
  * individual fragment's layout is:
  *
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager_list.xml
  *      complete}
  */
 public abstract class FragmentPagerAdapter extends PagerAdapter {
diff --git a/android/support/v13/app/FragmentStatePagerAdapter.java b/android/support/v13/app/FragmentStatePagerAdapter.java
index 8907fec..45a6bf5 100644
--- a/android/support/v13/app/FragmentStatePagerAdapter.java
+++ b/android/support/v13/app/FragmentStatePagerAdapter.java
@@ -51,18 +51,18 @@
  * <p>Here is an example implementation of a pager containing fragments of
  * lists:
  *
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStatePagerSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentStatePagerSupport.java
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
  *
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager_list</code> resource containing each
  * individual fragment's layout is:
  *
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
  *      complete}
  */
 public abstract class FragmentStatePagerAdapter extends PagerAdapter {
diff --git a/android/support/v14/preference/PreferenceFragment.java b/android/support/v14/preference/PreferenceFragment.java
index d1d9987..2421050 100644
--- a/android/support/v14/preference/PreferenceFragment.java
+++ b/android/support/v14/preference/PreferenceFragment.java
@@ -103,13 +103,13 @@
  * <p>The following sample code shows a simple preference fragment that is
  * populated from a resource.  The resource it loads is:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
  *
  * <p>The fragment implementation itself simply populates the preferences
  * when created.  Note that the preferences framework takes care of loading
  * the current values out of the app preferences and writing them when changed:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferences.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferences.java
  *      support_fragment}
  *
  * @see Preference
diff --git a/android/support/v17/leanback/app/BaseFragment.java b/android/support/v17/leanback/app/BaseFragment.java
index 7686c5c..bdb213f 100644
--- a/android/support/v17/leanback/app/BaseFragment.java
+++ b/android/support/v17/leanback/app/BaseFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BaseSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -15,6 +18,8 @@
 
 import android.annotation.SuppressLint;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.transition.TransitionListener;
 import android.support.v17.leanback.util.StateMachine;
@@ -177,7 +182,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         mStateMachine.fireEvent(EVT_ON_CREATEVIEW);
     }
@@ -267,6 +272,10 @@
     void onExecuteEntranceTransition() {
         // wait till views get their initial position before start transition
         final View view = getView();
+        if (view == null) {
+            // fragment view destroyed, transition not needed
+            return;
+        }
         view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
diff --git a/android/support/v17/leanback/app/BaseRowFragment.java b/android/support/v17/leanback/app/BaseRowFragment.java
index 5a83b47..2d79f3e 100644
--- a/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/android/support/v17/leanback/app/BaseRowFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BaseRowSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -13,8 +16,9 @@
  */
 package android.support.v17.leanback.app;
 
-import android.app.Fragment;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ListRow;
 import android.support.v17.leanback.widget.ObjectAdapter;
@@ -22,6 +26,7 @@
 import android.support.v17.leanback.widget.PresenterSelector;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -75,7 +80,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         if (savedInstanceState != null) {
             mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
         }
diff --git a/android/support/v17/leanback/app/BaseRowSupportFragment.java b/android/support/v17/leanback/app/BaseRowSupportFragment.java
index bf49295..dba78da 100644
--- a/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BaseRowFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -16,8 +13,9 @@
  */
 package android.support.v17.leanback.app;
 
-import android.support.v4.app.Fragment;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ListRow;
 import android.support.v17.leanback.widget.ObjectAdapter;
@@ -25,6 +23,7 @@
 import android.support.v17.leanback.widget.PresenterSelector;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -78,7 +77,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         if (savedInstanceState != null) {
             mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
         }
diff --git a/android/support/v17/leanback/app/BaseSupportFragment.java b/android/support/v17/leanback/app/BaseSupportFragment.java
index 213ed83..d89cf39 100644
--- a/android/support/v17/leanback/app/BaseSupportFragment.java
+++ b/android/support/v17/leanback/app/BaseSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BaseFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -18,6 +15,8 @@
 
 import android.annotation.SuppressLint;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.transition.TransitionListener;
 import android.support.v17.leanback.util.StateMachine;
@@ -180,7 +179,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         mStateMachine.fireEvent(EVT_ON_CREATEVIEW);
     }
@@ -270,6 +269,10 @@
     void onExecuteEntranceTransition() {
         // wait till views get their initial position before start transition
         final View view = getView();
+        if (view == null) {
+            // fragment view destroyed, transition not needed
+            return;
+        }
         view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
diff --git a/android/support/v17/leanback/app/BrandedFragment.java b/android/support/v17/leanback/app/BrandedFragment.java
index 35350e4..1f6ad29 100644
--- a/android/support/v17/leanback/app/BrandedFragment.java
+++ b/android/support/v17/leanback/app/BrandedFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrandedSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -13,13 +16,15 @@
  */
 package android.support.v17.leanback.app;
 
-import android.app.Fragment;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.SearchOrbView;
 import android.support.v17.leanback.widget.TitleHelper;
 import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.app.Fragment;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -143,7 +148,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         if (savedInstanceState != null) {
             mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
diff --git a/android/support/v17/leanback/app/BrandedSupportFragment.java b/android/support/v17/leanback/app/BrandedSupportFragment.java
index 9c42780..306e1f1 100644
--- a/android/support/v17/leanback/app/BrandedSupportFragment.java
+++ b/android/support/v17/leanback/app/BrandedSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrandedFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -16,13 +13,15 @@
  */
 package android.support.v17.leanback.app;
 
-import android.support.v4.app.Fragment;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.SearchOrbView;
 import android.support.v17.leanback.widget.TitleHelper;
 import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.support.v4.app.Fragment;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -146,7 +145,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         if (savedInstanceState != null) {
             mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java
index 8edaab6..f377389 100644
--- a/android/support/v17/leanback/app/BrowseFragment.java
+++ b/android/support/v17/leanback/app/BrowseFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrowseSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -15,10 +18,6 @@
 
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
 
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
-import android.app.FragmentTransaction;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -45,6 +44,10 @@
 import android.support.v17.leanback.widget.ScaleFrameLayout;
 import android.support.v17.leanback.widget.TitleViewAdapter;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentTransaction;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -1110,7 +1113,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        final Context context = FragmentUtil.getContext(this);
+        final Context context = FragmentUtil.getContext(BrowseFragment.this);
         TypedArray ta = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
         mContainerListMarginStart = (int) ta.getDimension(
                 R.styleable.LeanbackTheme_browseRowsMarginStart, context.getResources()
@@ -1278,7 +1281,7 @@
     }
 
     void createHeadersTransition() {
-        mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+        mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
                 mShowingHeaders
                         ? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
 
@@ -1710,7 +1713,7 @@
 
     @Override
     protected Object createEntranceTransition() {
-        return TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+        return TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
                 R.transition.lb_browse_entrance_transition);
     }
 
diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java
index 2665b2a..03b3c8a 100644
--- a/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrowseFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -18,10 +15,6 @@
 
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
 
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentManager.BackStackEntry;
-import android.support.v4.app.FragmentTransaction;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -48,6 +41,10 @@
 import android.support.v17.leanback.widget.ScaleFrameLayout;
 import android.support.v17.leanback.widget.TitleViewAdapter;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.support.v4.app.FragmentTransaction;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
diff --git a/android/support/v17/leanback/app/DetailsFragment.java b/android/support/v17/leanback/app/DetailsFragment.java
index 2c4e24a..3655963 100644
--- a/android/support/v17/leanback/app/DetailsFragment.java
+++ b/android/support/v17/leanback/app/DetailsFragment.java
@@ -1,3 +1,9 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsSupportFragment.java.  DO NOT MODIFY. */
+
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -771,7 +777,7 @@
 
     @Override
     protected Object createEntranceTransition() {
-        return TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+        return TransitionHelper.loadTransition(FragmentUtil.getContext(DetailsFragment.this),
                 R.transition.lb_details_enter_transition);
     }
 
diff --git a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
index 05dfb3a..223b8ef 100644
--- a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
+++ b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from {}DetailsSupportFragmentBackgroundController.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2017 The Android Open Source Project
  *
@@ -16,7 +19,6 @@
 package android.support.v17.leanback.app;
 
 import android.animation.PropertyValuesHolder;
-import android.app.Fragment;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -30,6 +32,7 @@
 import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.DetailsParallaxDrawable;
 import android.support.v17.leanback.widget.ParallaxTarget;
+import android.app.Fragment;
 
 /**
  * Controller for DetailsFragment parallax background and embedded video play.
diff --git a/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
index 4a0be6e..aac74ba 100644
--- a/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
+++ b/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoDetailsFragmentBackgroundController.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2017 The Android Open Source Project
  *
@@ -19,7 +16,6 @@
 package android.support.v17.leanback.app;
 
 import android.animation.PropertyValuesHolder;
-import android.support.v4.app.Fragment;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -33,6 +29,7 @@
 import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.DetailsParallaxDrawable;
 import android.support.v17.leanback.widget.ParallaxTarget;
+import android.support.v4.app.Fragment;
 
 /**
  * Controller for DetailsSupportFragment parallax background and embedded video play.
diff --git a/android/support/v17/leanback/app/ErrorFragment.java b/android/support/v17/leanback/app/ErrorFragment.java
index c35fcdc..2896d0f 100644
--- a/android/support/v17/leanback/app/ErrorFragment.java
+++ b/android/support/v17/leanback/app/ErrorFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from ErrorSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/ErrorSupportFragment.java b/android/support/v17/leanback/app/ErrorSupportFragment.java
index 179e2e9..55e7d92 100644
--- a/android/support/v17/leanback/app/ErrorSupportFragment.java
+++ b/android/support/v17/leanback/app/ErrorSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from ErrorFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/GuidedStepFragment.java b/android/support/v17/leanback/app/GuidedStepFragment.java
index a01cf26..2b7f2d0 100644
--- a/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from GuidedStepSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -17,11 +20,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
-import android.app.FragmentTransaction;
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
@@ -37,6 +35,11 @@
 import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
 import android.support.v4.app.ActivityCompat;
+import android.app.Fragment;
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentTransaction;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.util.TypedValue;
@@ -1131,7 +1134,7 @@
         } else {
             // when there are two actions panel, we need adjust the weight of action to
             // guidedActionContentWidthWeightTwoPanels.
-            Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(this);
+            Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(GuidedStepFragment.this);
             TypedValue typedValue = new TypedValue();
             if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels,
                     typedValue, true)) {
@@ -1338,7 +1341,7 @@
     private void resolveTheme() {
         // Look up the guidedStepTheme in the currently specified theme.  If it exists,
         // replace the theme with its value.
-        Context context = FragmentUtil.getContext(this);
+        Context context = FragmentUtil.getContext(GuidedStepFragment.this);
         int theme = onProvideTheme();
         if (theme == -1 && !isGuidedStepTheme(context)) {
             // Look up the guidedStepTheme in the activity's currently specified theme.  If it
diff --git a/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index ed34548..aeb2d33 100644
--- a/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from GuidedStepFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -20,11 +17,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentManager.BackStackEntry;
-import android.support.v4.app.FragmentTransaction;
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
@@ -40,6 +32,11 @@
 import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
 import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.support.v4.app.FragmentTransaction;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.util.TypedValue;
diff --git a/android/support/v17/leanback/app/HeadersFragment.java b/android/support/v17/leanback/app/HeadersFragment.java
index 724fa41..dd037d2 100644
--- a/android/support/v17/leanback/app/HeadersFragment.java
+++ b/android/support/v17/leanback/app/HeadersFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from HeadersSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -20,6 +23,8 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.ClassPresenterSelector;
 import android.support.v17.leanback.widget.DividerPresenter;
@@ -160,7 +165,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         final VerticalGridView listView = getVerticalGridView();
         if (listView == null) {
diff --git a/android/support/v17/leanback/app/HeadersSupportFragment.java b/android/support/v17/leanback/app/HeadersSupportFragment.java
index be867cb..56c85af 100644
--- a/android/support/v17/leanback/app/HeadersSupportFragment.java
+++ b/android/support/v17/leanback/app/HeadersSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from HeadersFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -23,6 +20,8 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.ClassPresenterSelector;
 import android.support.v17.leanback.widget.DividerPresenter;
@@ -163,7 +162,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         final VerticalGridView listView = getVerticalGridView();
         if (listView == null) {
diff --git a/android/support/v17/leanback/app/MediaControllerGlue.java b/android/support/v17/leanback/app/MediaControllerGlue.java
deleted file mode 100644
index 7949bfb..0000000
--- a/android/support/v17/leanback/app/MediaControllerGlue.java
+++ /dev/null
@@ -1,236 +0,0 @@
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-/**
- * A helper class for implementing a glue layer between a
- * {@link PlaybackOverlayFragment} and a
- * {@link android.support.v4.media.session.MediaControllerCompat}.
- * @deprecated Use {@link android.support.v17.leanback.media.MediaControllerGlue}.
- */
-@Deprecated
-public abstract class MediaControllerGlue extends PlaybackControlGlue {
-    static final String TAG = "MediaControllerGlue";
-    static final boolean DEBUG = false;
-
-    MediaControllerCompat mMediaController;
-
-    private final MediaControllerCompat.Callback mCallback = new MediaControllerCompat.Callback() {
-        @Override
-        public void onMetadataChanged(MediaMetadataCompat metadata) {
-            if (DEBUG) Log.v(TAG, "onMetadataChanged");
-            MediaControllerGlue.this.onMetadataChanged();
-        }
-        @Override
-        public void onPlaybackStateChanged(PlaybackStateCompat state) {
-            if (DEBUG) Log.v(TAG, "onPlaybackStateChanged");
-            onStateChanged();
-        }
-        @Override
-        public void onSessionDestroyed() {
-            if (DEBUG) Log.v(TAG, "onSessionDestroyed");
-            mMediaController = null;
-        }
-        @Override
-        public void onSessionEvent(String event, Bundle extras) {
-            if (DEBUG) Log.v(TAG, "onSessionEvent");
-        }
-    };
-
-    /**
-     * Constructor for the glue.
-     *
-     * <p>The {@link PlaybackOverlayFragment} must be passed in.
-     * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
-     * {@link android.support.v17.leanback.app.PlaybackControlGlue.InputEventHandler}
-     * will be set on the fragment.
-     * </p>
-     *
-     * @param context
-     * @param fragment
-     * @param seekSpeeds Array of seek speeds for fast forward and rewind.
-     */
-    public MediaControllerGlue(Context context,
-                               PlaybackOverlayFragment fragment,
-                               int[] seekSpeeds) {
-        super(context, fragment, seekSpeeds);
-    }
-
-    /**
-     * Constructor for the glue.
-     *
-     * <p>The {@link PlaybackOverlayFragment} must be passed in.
-     * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
-     * {@link android.support.v17.leanback.app.PlaybackControlGlue.InputEventHandler}
-     * will be set on the fragment.
-     * </p>
-     *
-     * @param context
-     * @param fragment
-     * @param fastForwardSpeeds Array of seek speeds for fast forward.
-     * @param rewindSpeeds Array of seek speeds for rewind.
-     */
-    public MediaControllerGlue(Context context,
-                               PlaybackOverlayFragment fragment,
-                               int[] fastForwardSpeeds,
-                               int[] rewindSpeeds) {
-        super(context, fragment, fastForwardSpeeds, rewindSpeeds);
-    }
-
-    /**
-     * Attaches to the given media controller.
-     */
-    public void attachToMediaController(MediaControllerCompat mediaController) {
-        if (mediaController != mMediaController) {
-            if (DEBUG) Log.v(TAG, "New media controller " + mediaController);
-            detach();
-            mMediaController = mediaController;
-            if (mMediaController != null) {
-                mMediaController.registerCallback(mCallback);
-            }
-            onMetadataChanged();
-            onStateChanged();
-        }
-    }
-
-    /**
-     * Detaches from the media controller.  Must be called when the object is no longer
-     * needed.
-     */
-    public void detach() {
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mCallback);
-        }
-        mMediaController = null;
-    }
-
-    /**
-     * Returns the media controller currently attached.
-     */
-    public final MediaControllerCompat getMediaController() {
-        return mMediaController;
-    }
-
-    @Override
-    public boolean hasValidMedia() {
-        return mMediaController != null && mMediaController.getMetadata() != null;
-    }
-
-    @Override
-    public boolean isMediaPlaying() {
-        return mMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
-    }
-
-    @Override
-    public int getCurrentSpeedId() {
-        int speed = (int) mMediaController.getPlaybackState().getPlaybackSpeed();
-        if (speed == 0) {
-            return PLAYBACK_SPEED_PAUSED;
-        } else if (speed == 1) {
-            return PLAYBACK_SPEED_NORMAL;
-        } else if (speed > 0) {
-            int[] seekSpeeds = getFastForwardSpeeds();
-            for (int index = 0; index < seekSpeeds.length; index++) {
-                if (speed == seekSpeeds[index]) {
-                    return PLAYBACK_SPEED_FAST_L0 + index;
-                }
-            }
-        } else {
-            int[] seekSpeeds = getRewindSpeeds();
-            for (int index = 0; index < seekSpeeds.length; index++) {
-                if (-speed == seekSpeeds[index]) {
-                    return -PLAYBACK_SPEED_FAST_L0 - index;
-                }
-            }
-        }
-        Log.w(TAG, "Couldn't find index for speed " + speed);
-        return PLAYBACK_SPEED_INVALID;
-    }
-
-    @Override
-    public CharSequence getMediaTitle() {
-        return mMediaController.getMetadata().getDescription().getTitle();
-    }
-
-    @Override
-    public CharSequence getMediaSubtitle() {
-        return mMediaController.getMetadata().getDescription().getSubtitle();
-    }
-
-    @Override
-    public int getMediaDuration() {
-        return (int) mMediaController.getMetadata().getLong(
-                MediaMetadataCompat.METADATA_KEY_DURATION);
-    }
-
-    @Override
-    public int getCurrentPosition() {
-        return (int) mMediaController.getPlaybackState().getPosition();
-    }
-
-    @Override
-    public Drawable getMediaArt() {
-        Bitmap bitmap = mMediaController.getMetadata().getDescription().getIconBitmap();
-        return bitmap == null ? null : new BitmapDrawable(getContext().getResources(), bitmap);
-    }
-
-    @Override
-    public long getSupportedActions() {
-        long result = 0;
-        long actions = mMediaController.getPlaybackState().getActions();
-        if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
-            result |= ACTION_PLAY_PAUSE;
-        }
-        if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
-            result |= ACTION_SKIP_TO_NEXT;
-        }
-        if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
-            result |= ACTION_SKIP_TO_PREVIOUS;
-        }
-        if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
-            result |= ACTION_FAST_FORWARD;
-        }
-        if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
-            result |= ACTION_REWIND;
-        }
-        return result;
-    }
-
-    @Override
-    protected void startPlayback(int speed) {
-        if (DEBUG) Log.v(TAG, "startPlayback speed " + speed);
-        if (speed == PLAYBACK_SPEED_NORMAL) {
-            mMediaController.getTransportControls().play();
-        } else if (speed > 0) {
-            mMediaController.getTransportControls().fastForward();
-        } else {
-            mMediaController.getTransportControls().rewind();
-        }
-    }
-
-    @Override
-    protected void pausePlayback() {
-        if (DEBUG) Log.v(TAG, "pausePlayback");
-        mMediaController.getTransportControls().pause();
-    }
-
-    @Override
-    protected void skipToNext() {
-        if (DEBUG) Log.v(TAG, "skipToNext");
-        mMediaController.getTransportControls().skipToNext();
-    }
-
-    @Override
-    protected void skipToPrevious() {
-        if (DEBUG) Log.v(TAG, "skipToPrevious");
-        mMediaController.getTransportControls().skipToPrevious();
-    }
-}
diff --git a/android/support/v17/leanback/app/OnboardingFragment.java b/android/support/v17/leanback/app/OnboardingFragment.java
index 22dd211..b69d5a7 100644
--- a/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/android/support/v17/leanback/app/OnboardingFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from OnboardingSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -22,14 +25,15 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.app.Fragment;
 import android.content.Context;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.PagingIndicator;
+import android.app.Fragment;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
@@ -340,7 +344,7 @@
         if (mStartButtonTextSet) {
             ((Button) mStartButton).setText(mStartButtonText);
         }
-        final Context context = FragmentUtil.getContext(this);
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
         if (sSlideDistance == 0) {
             sSlideDistance = (int) (SLIDE_DISTANCE * context.getResources()
                     .getDisplayMetrics().scaledDensity);
@@ -350,7 +354,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         if (savedInstanceState == null) {
             mCurrentPageIndex = 0;
@@ -538,7 +542,7 @@
     }
 
     private void resolveTheme() {
-        final Context context = FragmentUtil.getContext(this);
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
         int theme = onProvideTheme();
         if (theme == -1) {
             // Look up the onboardingTheme in the activity's currently specified theme. If it
@@ -592,7 +596,7 @@
     }
 
     boolean startLogoAnimation() {
-        final Context context = FragmentUtil.getContext(this);
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
         if (context == null) {
             return false;
         }
@@ -655,7 +659,7 @@
         View container = getView();
         // Create custom views.
         LayoutInflater inflater = getThemeInflater(LayoutInflater.from(
-                FragmentUtil.getContext(this)));
+                FragmentUtil.getContext(OnboardingFragment.this)));
         ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
                 R.id.background_container);
         View background = onCreateBackgroundView(inflater, backgroundContainer);
@@ -716,7 +720,7 @@
      *                          been done in the past, {@code false} otherwise
      */
     protected final void startEnterAnimation(boolean force) {
-        final Context context = FragmentUtil.getContext(this);
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
         if (context == null) {
             return;
         }
@@ -772,7 +776,7 @@
      * default fade and slide animation. Returning null will disable the animation.
      */
     protected Animator onCreateDescriptionAnimator() {
-        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(this),
+        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
                 R.animator.lb_onboarding_description_enter);
     }
 
@@ -781,7 +785,7 @@
      * default fade and slide animation. Returning null will disable the animation.
      */
     protected Animator onCreateTitleAnimator() {
-        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(this),
+        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
                 R.animator.lb_onboarding_title_enter);
     }
 
@@ -919,7 +923,7 @@
             }
         });
 
-        final Context context = FragmentUtil.getContext(this);
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
         // Animator for switching between page indicator and button.
         if (getCurrentPageIndex() == getPageCount() - 1) {
             mStartButton.setVisibility(View.VISIBLE);
diff --git a/android/support/v17/leanback/app/OnboardingSupportFragment.java b/android/support/v17/leanback/app/OnboardingSupportFragment.java
index a24ea4d..51cb2de 100644
--- a/android/support/v17/leanback/app/OnboardingSupportFragment.java
+++ b/android/support/v17/leanback/app/OnboardingSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from OnboardingFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -25,14 +22,15 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.support.v4.app.Fragment;
 import android.content.Context;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.PagingIndicator;
+import android.support.v4.app.Fragment;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
@@ -353,7 +351,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         if (savedInstanceState == null) {
             mCurrentPageIndex = 0;
diff --git a/android/support/v17/leanback/app/PlaybackControlGlue.java b/android/support/v17/leanback/app/PlaybackControlGlue.java
deleted file mode 100644
index d74fd11..0000000
--- a/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ /dev/null
@@ -1,337 +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.support.v17.leanback.app;
-
-import android.content.Context;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.View;
-
-/**
- * A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow}
- * and {@link PlaybackGlueHost} that implements a
- * recommended approach to handling standard playback control actions such as play/pause,
- * fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
- * is a glue layer in that manages the configuration of and interaction between the
- * leanback UI components by defining a functional interface to the media player.
- *
- * <p>You can instantiate a concrete subclass such as MediaPlayerGlue or you must
- * subclass this abstract helper.  To create a subclass you must implement all of the
- * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
- * {@link #onStateChanged()} appropriately.
- * </p>
- *
- * <p>To use an instance of the glue layer, first construct an instance.  Constructor parameters
- * inform the glue what speed levels are supported for fast forward/rewind.
- * </p>
- *
- * <p>If you have your own controls row you must pass it to {@link #setControlsRow}.
- * The row will be updated by the glue layer based on the media metadata and playback state.
- * Alternatively, you may call {@link #createControlsRowAndPresenter()} which will set a controls
- * row and return a row presenter you can use to present the row.
- * </p>
- *
- * <p>The helper sets a {@link android.support.v17.leanback.widget.SparseArrayObjectAdapter}
- * on the controls row as the primary actions adapter, and adds actions to it. You can provide
- * additional actions by overriding {@link #createPrimaryActionsAdapter}. This helper does not
- * deal in secondary actions so those you may add separately.
- * </p>
- *
- * <p>Provide a click listener on your fragment and if an action is clicked, call
- * {@link #onActionClicked}. If you set a listener by calling {@link #setOnItemViewClickedListener},
- * your listener will be called for all unhandled actions.
- * </p>
- *
- * <p>This helper implements a key event handler. If you pass a
- * {@link PlaybackOverlayFragment}, it will configure its
- * fragment to intercept all key events.  Otherwise, you should set the glue object as key event
- * handler to the ViewHolder when bound by your row presenter; see
- * {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
- * </p>
- *
- * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
- * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
- * {@link #getUpdatePeriod()} provides a recommended update period.
- * </p>
- * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue}
- */
-@Deprecated
-public abstract class PlaybackControlGlue extends
-        android.support.v17.leanback.media.PlaybackControlGlue {
-
-    OnItemViewClickedListener mExternalOnItemViewClickedListener;
-
-    /**
-     * Constructor for the glue.
-     *
-     * @param context
-     * @param seekSpeeds Array of seek speeds for fast forward and rewind.
-     */
-    public PlaybackControlGlue(Context context, int[] seekSpeeds) {
-        super(context, seekSpeeds, seekSpeeds);
-    }
-
-    /**
-     * Constructor for the glue.
-     *
-     * @param context
-     * @param fastForwardSpeeds Array of seek speeds for fast forward.
-     * @param rewindSpeeds Array of seek speeds for rewind.
-     */
-    public PlaybackControlGlue(Context context,
-                               int[] fastForwardSpeeds,
-                               int[] rewindSpeeds) {
-        super(context, fastForwardSpeeds, rewindSpeeds);
-    }
-
-    /**
-     * Constructor for the glue.
-     *
-     * @param context
-     * @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
-     * @param seekSpeeds Array of seek speeds for fast forward and rewind.
-     */
-    public PlaybackControlGlue(Context context,
-                               PlaybackOverlayFragment fragment,
-                               int[] seekSpeeds) {
-        this(context, fragment, seekSpeeds, seekSpeeds);
-    }
-
-    /**
-     * Constructor for the glue.
-     *
-     * @param context
-     * @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
-     * @param fastForwardSpeeds Array of seek speeds for fast forward.
-     * @param rewindSpeeds Array of seek speeds for rewind.
-     */
-    public PlaybackControlGlue(Context context,
-                               PlaybackOverlayFragment fragment,
-                               int[] fastForwardSpeeds,
-                               int[] rewindSpeeds) {
-        super(context, fastForwardSpeeds, rewindSpeeds);
-        setHost(fragment == null ? (PlaybackGlueHost) null : new PlaybackGlueHostOld(fragment));
-    }
-
-    @Override
-    protected void onAttachedToHost(PlaybackGlueHost host) {
-        super.onAttachedToHost(host);
-        if (host instanceof PlaybackGlueHostOld) {
-            ((PlaybackGlueHostOld) host).mGlue = this;
-        }
-    }
-
-    /**
-     * Returns the fragment.
-     */
-    public PlaybackOverlayFragment getFragment() {
-        if (getHost() instanceof PlaybackGlueHostOld) {
-            return ((PlaybackGlueHostOld)getHost()).mFragment;
-        }
-        return null;
-    }
-
-    /**
-     * Start playback at the given speed.
-     * @deprecated use {@link #play()} instead.
-     *
-     * @param speed The desired playback speed.  For normal playback this will be
-     *              {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
-     *              and negative values for rewind.
-     */
-    @Deprecated
-    protected void startPlayback(int speed) {}
-
-    /**
-     * Pause playback.
-     * @deprecated use {@link #pause()} instead.
-     */
-    @Deprecated
-    protected void pausePlayback() {}
-
-    /**
-     * Skip to the next track.
-     * @deprecated use {@link #next()} instead.
-     */
-    @Deprecated
-    protected void skipToNext() {}
-
-    /**
-     * Skip to the previous track.
-     * @deprecated use {@link #previous()} instead.
-     */
-    @Deprecated
-    protected void skipToPrevious() {}
-
-    @Override
-    public final void next() {
-        skipToNext();
-    }
-
-    @Override
-    public final void previous() {
-        skipToPrevious();
-    }
-
-    @Override
-    public final void play(int speed) {
-        startPlayback(speed);
-    }
-
-    @Override
-    public final void pause() {
-        pausePlayback();
-    }
-
-    /**
-     * This method invoked when the playback controls row has changed. The adapter
-     * containing this row should be notified.
-     */
-    protected void onRowChanged(PlaybackControlsRow row) {
-    }
-
-    /**
-     * Set the {@link OnItemViewClickedListener} to be called if the click event
-     * is not handled internally.
-     * @param listener
-     * @deprecated Don't call this. Instead use the listener on the fragment yourself.
-     */
-    @Deprecated
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        mExternalOnItemViewClickedListener = listener;
-    }
-
-    /**
-     * Returns the {@link OnItemViewClickedListener}.
-     * @deprecated Don't call this. Instead use the listener on the fragment yourself.
-     */
-    @Deprecated
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
-        return mExternalOnItemViewClickedListener;
-    }
-
-    @Override
-    protected void onCreateControlsRowAndPresenter() {
-        // backward compatible, we dont create row / presenter by default.
-        // User is expected to call createControlsRowAndPresenter() or setControlsRow()
-        // explicitly.
-    }
-
-    /**
-     * Helper method for instantiating a
-     * {@link android.support.v17.leanback.widget.PlaybackControlsRow} and corresponding
-     * {@link android.support.v17.leanback.widget.PlaybackControlsRowPresenter}.
-     */
-    public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
-        super.onCreateControlsRowAndPresenter();
-        return getControlsRowPresenter();
-    }
-
-    @Override
-    protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
-            PresenterSelector presenterSelector) {
-        return super.createPrimaryActionsAdapter(presenterSelector);
-    }
-
-    /**
-     * Interface allowing the application to handle input events.
-     * @deprecated Use
-     * {@link PlaybackGlueHost#setOnKeyInterceptListener(View.OnKeyListener)}.
-     */
-    @Deprecated
-    public interface InputEventHandler {
-        /**
-         * Called when an {@link InputEvent} is received.
-         *
-         * @return If the event should be consumed, return true. To allow the event to
-         * continue on to the next handler, return false.
-         */
-        boolean handleInputEvent(InputEvent event);
-    }
-
-    static final class PlaybackGlueHostOld extends PlaybackGlueHost {
-        final PlaybackOverlayFragment mFragment;
-        PlaybackControlGlue mGlue;
-        OnActionClickedListener mActionClickedListener;
-
-        public PlaybackGlueHostOld(PlaybackOverlayFragment fragment) {
-            mFragment = fragment;
-            mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
-                    if (item instanceof Action
-                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
-                            && mActionClickedListener != null) {
-                        mActionClickedListener.onActionClicked((Action) item);
-                    } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
-                        mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
-                                item, rowViewHolder, row);
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void setFadingEnabled(boolean enable) {
-            mFragment.setFadingEnabled(enable);
-        }
-
-        @Override
-        public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListener) {
-            mFragment.setEventHandler( new InputEventHandler() {
-                @Override
-                public boolean handleInputEvent(InputEvent event) {
-                    if (event instanceof KeyEvent) {
-                        KeyEvent keyEvent = (KeyEvent) event;
-                        return onKeyListener.onKey(null, keyEvent.getKeyCode(), keyEvent);
-                    }
-                    return false;
-                }
-            });
-        }
-
-        @Override
-        public void setOnActionClickedListener(final OnActionClickedListener listener) {
-            mActionClickedListener = listener;
-        }
-
-        @Override
-        public void setHostCallback(HostCallback callback) {
-            mFragment.setHostCallback(callback);
-        }
-
-        @Override
-        public void fadeOut() {
-            mFragment.fadeOut();
-        }
-
-        @Override
-        public void notifyPlaybackRowChanged() {
-            mGlue.onRowChanged(mGlue.getControlsRow());
-        }
-    }
-}
diff --git a/android/support/v17/leanback/app/PlaybackControlSupportGlue.java b/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
deleted file mode 100644
index b3d19ae..0000000
--- a/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/* This file is auto-generated from PlaybackControlGlue.java.  DO NOT MODIFY. */
-
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.View;
-
-/**
- * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue} and
- * {@link PlaybackSupportFragmentGlueHost} for {@link PlaybackSupportFragment}.
- */
-@Deprecated
-public abstract class PlaybackControlSupportGlue extends PlaybackControlGlue {
-    /**
-     * The adapter key for the first custom control on the left side
-     * of the predefined primary controls.
-     */
-    public static final int ACTION_CUSTOM_LEFT_FIRST = PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST;
-
-    /**
-     * The adapter key for the skip to previous control.
-     */
-    public static final int ACTION_SKIP_TO_PREVIOUS = PlaybackControlGlue.ACTION_SKIP_TO_PREVIOUS;
-
-    /**
-     * The adapter key for the rewind control.
-     */
-    public static final int ACTION_REWIND = PlaybackControlGlue.ACTION_REWIND;
-
-    /**
-     * The adapter key for the play/pause control.
-     */
-    public static final int ACTION_PLAY_PAUSE = PlaybackControlGlue.ACTION_PLAY_PAUSE;
-
-    /**
-     * The adapter key for the fast forward control.
-     */
-    public static final int ACTION_FAST_FORWARD = PlaybackControlGlue.ACTION_FAST_FORWARD;
-
-    /**
-     * The adapter key for the skip to next control.
-     */
-    public static final int ACTION_SKIP_TO_NEXT = PlaybackControlGlue.ACTION_SKIP_TO_NEXT;
-
-    /**
-     * The adapter key for the first custom control on the right side
-     * of the predefined primary controls.
-     */
-    public static final int ACTION_CUSTOM_RIGHT_FIRST =
-            PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST;
-
-    /**
-     * Invalid playback speed.
-     */
-    public static final int PLAYBACK_SPEED_INVALID = PlaybackControlGlue.PLAYBACK_SPEED_INVALID;
-
-    /**
-     * Speed representing playback state that is paused.
-     */
-    public static final int PLAYBACK_SPEED_PAUSED = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
-
-    /**
-     * Speed representing playback state that is playing normally.
-     */
-    public static final int PLAYBACK_SPEED_NORMAL = PlaybackControlGlue.PLAYBACK_SPEED_NORMAL;
-
-    /**
-     * The initial (level 0) fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L0 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
-
-    /**
-     * The level 1 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L1 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1;
-
-    /**
-     * The level 2 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L2 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L2;
-
-    /**
-     * The level 3 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L3 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L3;
-
-    /**
-     * The level 4 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L4 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L4;
-
-    public PlaybackControlSupportGlue(Context context, int[] seekSpeeds) {
-        this(context, null, seekSpeeds, seekSpeeds);
-    }
-
-    public PlaybackControlSupportGlue(
-            Context context, int[] fastForwardSpeeds, int[] rewindSpeeds) {
-        this(context, null, fastForwardSpeeds, rewindSpeeds);
-    }
-
-    public PlaybackControlSupportGlue(
-            Context context,
-            PlaybackOverlaySupportFragment fragment,
-            int[] seekSpeeds) {
-        this(context, fragment, seekSpeeds, seekSpeeds);
-    }
-
-    public PlaybackControlSupportGlue(
-            Context context,
-            PlaybackOverlaySupportFragment fragment,
-            int[] fastForwardSpeeds,
-            int[] rewindSpeeds) {
-        super(context, fastForwardSpeeds, rewindSpeeds);
-        setHost(fragment == null ? null : new PlaybackSupportGlueHostOld(fragment));
-    }
-
-    @Override
-    protected void onAttachedToHost(PlaybackGlueHost host) {
-        super.onAttachedToHost(host);
-        if (host instanceof PlaybackSupportGlueHostOld) {
-            ((PlaybackSupportGlueHostOld) host).mGlue = this;
-        }
-    }
-
-    static final class PlaybackSupportGlueHostOld extends PlaybackGlueHost {
-        final PlaybackOverlaySupportFragment mFragment;
-        PlaybackControlSupportGlue mGlue;
-        OnActionClickedListener mActionClickedListener;
-
-        public PlaybackSupportGlueHostOld(PlaybackOverlaySupportFragment fragment) {
-            mFragment = fragment;
-            mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
-                    if (item instanceof Action
-                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
-                            && mActionClickedListener != null) {
-                        mActionClickedListener.onActionClicked((Action) item);
-                    } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
-                        mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
-                                item, rowViewHolder, row);
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void setFadingEnabled(boolean enable) {
-            mFragment.setFadingEnabled(enable);
-        }
-
-        @Override
-        public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListenerr) {
-            mFragment.setEventHandler( new InputEventHandler() {
-                @Override
-                public boolean handleInputEvent(InputEvent event) {
-                    if (event instanceof KeyEvent) {
-                        KeyEvent keyEvent = (KeyEvent) event;
-                        return onKeyListenerr.onKey(null, keyEvent.getKeyCode(), keyEvent);
-                    }
-                    return false;
-                }
-            });
-        }
-
-        @Override
-        public void setOnActionClickedListener(final OnActionClickedListener listener) {
-            mActionClickedListener = listener;
-        }
-
-        @Override
-        public void setHostCallback(HostCallback callback) {
-            mFragment.setHostCallback(callback);
-        }
-
-        @Override
-        public void fadeOut() {
-            mFragment.fadeOut();
-        }
-
-        @Override
-        public void notifyPlaybackRowChanged() {
-            mGlue.onRowChanged(mGlue.getControlsRow());
-        }
-    }
-}
diff --git a/android/support/v17/leanback/app/PlaybackFragment.java b/android/support/v17/leanback/app/PlaybackFragment.java
index 68a1215..33e787c 100644
--- a/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/android/support/v17/leanback/app/PlaybackFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
@@ -18,13 +21,14 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.Fragment;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
@@ -45,6 +49,7 @@
 import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.view.InputEvent;
@@ -447,7 +452,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         // controls view are initially visible, make it invisible
         // if app has called hideControlsOverlay() before view created.
@@ -505,7 +510,7 @@
             }
         };
 
-        Context context = FragmentUtil.getContext(this);
+        Context context = FragmentUtil.getContext(PlaybackFragment.this);
         mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
         mBgFadeInAnimator.addUpdateListener(listener);
         mBgFadeInAnimator.addListener(mFadeListener);
@@ -540,7 +545,7 @@
             }
         };
 
-        Context context = FragmentUtil.getContext(this);
+        Context context = FragmentUtil.getContext(PlaybackFragment.this);
         mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
         mControlRowFadeInAnimator.addUpdateListener(updateListener);
         mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
@@ -570,7 +575,7 @@
             }
         };
 
-        Context context = FragmentUtil.getContext(this);
+        Context context = FragmentUtil.getContext(PlaybackFragment.this);
         mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
         mOtherRowFadeInAnimator.addUpdateListener(updateListener);
         mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
diff --git a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
index d537c3a..4a9d10f 100644
--- a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from {}PlaybackSupportFragmentGlueHost.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/android/support/v17/leanback/app/PlaybackOverlayFragment.java
deleted file mode 100644
index d4b532b..0000000
--- a/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ /dev/null
@@ -1,863 +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.v17.leanback.app;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.animation.LogAccelerateInterpolator;
-import android.support.v17.leanback.animation.LogDecelerateInterpolator;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.ItemAlignmentFacet;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * A fragment for displaying playback controls and related content.
- * <p>
- * A PlaybackOverlayFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
- * of {@link RowPresenter}.
- * </p>
- * <p>
- * An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
- * at position 0 in the adapter.
- * </p>
- * <p>
- *  This class is now deprecated, please us
- * </p>
- * @deprecated Use {@link PlaybackFragment}.
- */
-@Deprecated
-public class PlaybackOverlayFragment extends DetailsFragment {
-
-    /**
-     * No background.
-     */
-    public static final int BG_NONE = 0;
-
-    /**
-     * A dark translucent background.
-     */
-    public static final int BG_DARK = 1;
-
-    /**
-     * A light translucent background.
-     */
-    public static final int BG_LIGHT = 2;
-
-    /**
-     * Listener allowing the application to receive notification of fade in and/or fade out
-     * completion events.
-     */
-    public static class OnFadeCompleteListener {
-        public void onFadeInComplete() {
-        }
-        public void onFadeOutComplete() {
-        }
-    }
-
-    static final String TAG = "PlaybackOF";
-    static final boolean DEBUG = false;
-    private static final int ANIMATION_MULTIPLIER = 1;
-
-    static int START_FADE_OUT = 1;
-
-    // Fading status
-    static final int IDLE = 0;
-    private static final int IN = 1;
-    static final int OUT = 2;
-
-    private int mOtherRowsCenterToBottom;
-    private int mPaddingBottom;
-    private View mRootView;
-    private int mBackgroundType = BG_DARK;
-    private int mBgDarkColor;
-    private int mBgLightColor;
-    private int mShowTimeMs;
-    private int mMajorFadeTranslateY, mMinorFadeTranslateY;
-    int mAnimationTranslateY;
-    OnFadeCompleteListener mFadeCompleteListener;
-    private PlaybackControlGlue.InputEventHandler mInputEventHandler;
-    boolean mFadingEnabled = true;
-    int mFadingStatus = IDLE;
-    int mBgAlpha;
-    private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
-    private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
-    private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
-    private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
-    boolean mResetControlsToPrimaryActionsPending;
-    PlaybackGlueHost.HostCallback mHostCallback;
-
-    private final Animator.AnimatorListener mFadeListener =
-            new Animator.AnimatorListener() {
-        @Override
-        public void onAnimationStart(Animator animation) {
-            enableVerticalGridAnimations(false);
-        }
-        @Override
-        public void onAnimationRepeat(Animator animation) {
-        }
-        @Override
-        public void onAnimationCancel(Animator animation) {
-        }
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
-            if (mBgAlpha > 0) {
-                enableVerticalGridAnimations(true);
-                startFadeTimer();
-                if (mFadeCompleteListener != null) {
-                    mFadeCompleteListener.onFadeInComplete();
-                }
-            } else {
-                VerticalGridView verticalView = getVerticalGridView();
-                // reset focus to the primary actions only if the selected row was the controls row
-                if (verticalView != null && verticalView.getSelectedPosition() == 0) {
-                    resetControlsToPrimaryActions(null);
-                }
-                if (mFadeCompleteListener != null) {
-                    mFadeCompleteListener.onFadeOutComplete();
-                }
-            }
-            mFadingStatus = IDLE;
-        }
-    };
-
-    static class FadeHandler extends Handler {
-        @Override
-        public void handleMessage(Message message) {
-            PlaybackOverlayFragment fragment;
-            if (message.what == START_FADE_OUT) {
-                fragment = ((WeakReference<PlaybackOverlayFragment>) message.obj).get();
-                if (fragment != null && fragment.mFadingEnabled) {
-                    fragment.fade(false);
-                }
-            }
-        }
-    }
-
-    static final Handler sHandler = new FadeHandler();
-
-    final WeakReference<PlaybackOverlayFragment> mFragmentReference =  new WeakReference(this);
-
-    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
-            new VerticalGridView.OnTouchInterceptListener() {
-        @Override
-        public boolean onInterceptTouchEvent(MotionEvent event) {
-            return onInterceptInputEvent(event);
-        }
-    };
-
-    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
-            new VerticalGridView.OnKeyInterceptListener() {
-        @Override
-        public boolean onInterceptKeyEvent(KeyEvent event) {
-            return onInterceptInputEvent(event);
-        }
-    };
-
-    void setBgAlpha(int alpha) {
-        mBgAlpha = alpha;
-        if (mRootView != null) {
-            mRootView.getBackground().setAlpha(alpha);
-        }
-    }
-
-    void enableVerticalGridAnimations(boolean enable) {
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().setAnimateChildLayout(enable);
-        }
-    }
-
-    void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
-        if (vh == null && getVerticalGridView() != null) {
-            vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
-        }
-        if (vh == null) {
-            mResetControlsToPrimaryActionsPending = true;
-        } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
-            mResetControlsToPrimaryActionsPending = false;
-            ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
-                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
-        }
-    }
-
-    /**
-     * Enables or disables view fading.  If enabled,
-     * the view will be faded in when the fragment starts,
-     * and will fade out after a time period.  The timeout
-     * period is reset each time {@link #tickle} is called.
-     *
-     */
-    public void setFadingEnabled(boolean enabled) {
-        if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
-        if (enabled != mFadingEnabled) {
-            mFadingEnabled = enabled;
-            if (mFadingEnabled) {
-                if (isResumed() && mFadingStatus == IDLE
-                        && !sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
-                    startFadeTimer();
-                }
-            } else {
-                // Ensure fully opaque
-                sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-                fade(true);
-            }
-        }
-    }
-
-    /**
-     * Returns true if view fading is enabled.
-     */
-    public boolean isFadingEnabled() {
-        return mFadingEnabled;
-    }
-
-    /**
-     * Sets the listener to be called when fade in or out has completed.
-     */
-    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
-        mFadeCompleteListener = listener;
-    }
-
-    /**
-     * Returns the listener to be called when fade in or out has completed.
-     */
-    public OnFadeCompleteListener getFadeCompleteListener() {
-        return mFadeCompleteListener;
-    }
-
-    @Deprecated
-    public interface InputEventHandler extends PlaybackControlGlue.InputEventHandler {
-    }
-
-    /**
-     * Sets the input event handler.
-     */
-    @Deprecated
-    public final void setInputEventHandler(InputEventHandler handler) {
-        mInputEventHandler = handler;
-    }
-
-    /**
-     * Returns the input event handler.
-     */
-    @Deprecated
-    public final InputEventHandler getInputEventHandler() {
-        return (InputEventHandler)mInputEventHandler;
-    }
-
-    /**
-     * Sets the input event handler.
-     */
-    public final void setEventHandler(PlaybackControlGlue.InputEventHandler handler) {
-        mInputEventHandler = handler;
-    }
-
-    /**
-     * Returns the input event handler.
-     */
-    public final PlaybackControlGlue.InputEventHandler getEventHandler() {
-        return mInputEventHandler;
-    }
-
-    /**
-     * Tickles the playback controls.  Fades in the view if it was faded out,
-     * otherwise resets the fade out timer.  Tickling on input events is handled
-     * by the fragment.
-     */
-    public void tickle() {
-        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
-        if (!mFadingEnabled || !isResumed()) {
-            return;
-        }
-        if (sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
-            // Restart the timer
-            startFadeTimer();
-        } else {
-            fade(true);
-        }
-    }
-
-    /**
-     * Fades out the playback overlay immediately.
-     */
-    public void fadeOut() {
-        sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-        fade(false);
-    }
-
-    /**
-     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
-     * take appropriate actions to take action when the hosting fragment starts/stops processing.
-     */
-    void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
-        this.mHostCallback = hostCallback;
-    }
-
-    @Override
-    public void onStop() {
-        if (mHostCallback != null) {
-            mHostCallback.onHostStop();
-        }
-        super.onStop();
-    }
-
-    @Override
-    public void onPause() {
-        if (mHostCallback != null) {
-            mHostCallback.onHostPause();
-        }
-        super.onPause();
-    }
-
-    private boolean areControlsHidden() {
-        return mFadingStatus == IDLE && mBgAlpha == 0;
-    }
-
-    boolean onInterceptInputEvent(InputEvent event) {
-        final boolean controlsHidden = areControlsHidden();
-        if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
-        boolean consumeEvent = false;
-        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
-
-        if (mInputEventHandler != null) {
-            consumeEvent = mInputEventHandler.handleInputEvent(event);
-        }
-        if (event instanceof KeyEvent) {
-            keyCode = ((KeyEvent) event).getKeyCode();
-        }
-
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                // Event may be consumed; regardless, if controls are hidden then these keys will
-                // bring up the controls.
-                if (controlsHidden) {
-                    consumeEvent = true;
-                }
-                tickle();
-                break;
-            case KeyEvent.KEYCODE_BACK:
-            case KeyEvent.KEYCODE_ESCAPE:
-                // If fading enabled and controls are not hidden, back will be consumed to fade
-                // them out (even if the key was consumed by the handler).
-                if (mFadingEnabled && !controlsHidden) {
-                    consumeEvent = true;
-                    sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-                    fade(false);
-                } else if (consumeEvent) {
-                    tickle();
-                }
-                break;
-            default:
-                if (consumeEvent) {
-                    tickle();
-                }
-        }
-        return consumeEvent;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mFadingEnabled) {
-            setBgAlpha(0);
-            fade(true);
-        }
-        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
-        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
-        if (mHostCallback != null) {
-            mHostCallback.onHostResume();
-        }
-    }
-
-    void startFadeTimer() {
-        sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-        sHandler.sendMessageDelayed(sHandler.obtainMessage(START_FADE_OUT, mFragmentReference),
-                mShowTimeMs);
-    }
-
-    private static ValueAnimator loadAnimator(Context context, int resId) {
-        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
-        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
-        return animator;
-    }
-
-    private void loadBgAnimator() {
-        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                setBgAlpha((Integer) arg0.getAnimatedValue());
-            }
-        };
-
-        Context context = FragmentUtil.getContext(this);
-        mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
-        mBgFadeInAnimator.addUpdateListener(listener);
-        mBgFadeInAnimator.addListener(mFadeListener);
-
-        mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
-        mBgFadeOutAnimator.addUpdateListener(listener);
-        mBgFadeOutAnimator.addListener(mFadeListener);
-    }
-
-    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
-    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
-
-    View getControlRowView() {
-        if (getVerticalGridView() == null) {
-            return null;
-        }
-        RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
-        if (vh == null) {
-            return null;
-        }
-        return vh.itemView;
-    }
-
-    private void loadControlRowAnimator() {
-        final AnimatorListener listener = new AnimatorListener() {
-            @Override
-            void getViews(ArrayList<View> views) {
-                View view = getControlRowView();
-                if (view != null) {
-                    views.add(view);
-                }
-            }
-        };
-        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                View view = getControlRowView();
-                if (view != null) {
-                    final float fraction = (Float) arg0.getAnimatedValue();
-                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
-                    view.setAlpha(fraction);
-                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
-                }
-            }
-        };
-
-        Context context = FragmentUtil.getContext(this);
-        mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
-        mControlRowFadeInAnimator.addUpdateListener(updateListener);
-        mControlRowFadeInAnimator.addListener(listener);
-        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mControlRowFadeOutAnimator = loadAnimator(context,
-                R.animator.lb_playback_controls_fade_out);
-        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
-        mControlRowFadeOutAnimator.addListener(listener);
-        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
-    }
-
-    private void loadOtherRowAnimator() {
-        final AnimatorListener listener = new AnimatorListener() {
-            @Override
-            void getViews(ArrayList<View> views) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                final int count = getVerticalGridView().getChildCount();
-                for (int i = 0; i < count; i++) {
-                    View view = getVerticalGridView().getChildAt(i);
-                    if (view != null) {
-                        views.add(view);
-                    }
-                }
-            }
-        };
-        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                final float fraction = (Float) arg0.getAnimatedValue();
-                for (View view : listener.mViews) {
-                    if (getVerticalGridView().getChildPosition(view) > 0) {
-                        view.setAlpha(fraction);
-                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
-                    }
-                }
-            }
-        };
-
-        Context context = FragmentUtil.getContext(this);
-        mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
-        mOtherRowFadeInAnimator.addListener(listener);
-        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
-        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
-        mOtherRowFadeOutAnimator.addListener(listener);
-        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
-        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
-    }
-
-    private void loadDescriptionAnimator() {
-        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
-                        getVerticalGridView().findViewHolderForPosition(0);
-                if (adapterVh != null && adapterVh.getViewHolder()
-                        instanceof PlaybackControlsRowPresenter.ViewHolder) {
-                    final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
-                            adapterVh.getViewHolder()).mDescriptionViewHolder;
-                    if (vh != null) {
-                        vh.view.setAlpha((Float) arg0.getAnimatedValue());
-                    }
-                }
-            }
-        };
-
-        Context context = FragmentUtil.getContext(this);
-        mDescriptionFadeInAnimator = loadAnimator(context,
-                R.animator.lb_playback_description_fade_in);
-        mDescriptionFadeInAnimator.addUpdateListener(listener);
-        mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mDescriptionFadeOutAnimator = loadAnimator(context,
-                R.animator.lb_playback_description_fade_out);
-        mDescriptionFadeOutAnimator.addUpdateListener(listener);
-    }
-
-    void fade(boolean fadeIn) {
-        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
-        if (getView() == null) {
-            return;
-        }
-        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
-            if (DEBUG) Log.v(TAG, "requested fade in progress");
-            return;
-        }
-        if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
-            if (DEBUG) Log.v(TAG, "fade is no-op");
-            return;
-        }
-
-        mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0
-                ? mMajorFadeTranslateY : mMinorFadeTranslateY;
-
-        if (mFadingStatus == IDLE) {
-            if (fadeIn) {
-                mBgFadeInAnimator.start();
-                mControlRowFadeInAnimator.start();
-                mOtherRowFadeInAnimator.start();
-                mDescriptionFadeInAnimator.start();
-            } else {
-                mBgFadeOutAnimator.start();
-                mControlRowFadeOutAnimator.start();
-                mOtherRowFadeOutAnimator.start();
-                mDescriptionFadeOutAnimator.start();
-            }
-        } else {
-            if (fadeIn) {
-                mBgFadeOutAnimator.reverse();
-                mControlRowFadeOutAnimator.reverse();
-                mOtherRowFadeOutAnimator.reverse();
-                mDescriptionFadeOutAnimator.reverse();
-            } else {
-                mBgFadeInAnimator.reverse();
-                mControlRowFadeInAnimator.reverse();
-                mOtherRowFadeInAnimator.reverse();
-                mDescriptionFadeInAnimator.reverse();
-            }
-        }
-        getView().announceForAccessibility(getString(fadeIn ? R.string.lb_playback_controls_shown
-                : R.string.lb_playback_controls_hidden));
-
-        // If fading in while control row is focused, set initial translationY so
-        // views slide in from below.
-        if (fadeIn && mFadingStatus == IDLE) {
-            final int count = getVerticalGridView().getChildCount();
-            for (int i = 0; i < count; i++) {
-                getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
-            }
-        }
-
-        mFadingStatus = fadeIn ? IN : OUT;
-    }
-
-    /**
-     * Sets the list of rows for the fragment.
-     */
-    @Override
-    public void setAdapter(ObjectAdapter adapter) {
-        if (getAdapter() != null) {
-            getAdapter().unregisterObserver(mObserver);
-        }
-        super.setAdapter(adapter);
-        if (adapter != null) {
-            adapter.registerObserver(mObserver);
-        }
-    }
-
-    @Override
-    protected void setupPresenter(Presenter rowPresenter) {
-        if (rowPresenter instanceof PlaybackRowPresenter) {
-            if (rowPresenter.getFacet(ItemAlignmentFacet.class) == null) {
-                ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
-                ItemAlignmentFacet.ItemAlignmentDef def =
-                        new ItemAlignmentFacet.ItemAlignmentDef();
-                def.setItemAlignmentOffset(0);
-                def.setItemAlignmentOffsetPercent(100);
-                itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
-                        {def});
-                rowPresenter.setFacet(ItemAlignmentFacet.class, itemAlignment);
-            }
-        } else {
-            super.setupPresenter(rowPresenter);
-        }
-    }
-
-    @Override
-    void setVerticalGridViewLayout(VerticalGridView listview) {
-        if (listview == null) {
-            return;
-        }
-
-        // we set the base line of alignment to -paddingBottom
-        listview.setWindowAlignmentOffset(-mPaddingBottom);
-        listview.setWindowAlignmentOffsetPercent(
-                VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-
-        // align other rows that arent the last to center of screen, since our baseline is
-        // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
-        listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
-        listview.setItemAlignmentOffsetPercent(50);
-
-        // Push last row to the bottom padding
-        // Padding affects alignment when last row is focused
-        listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
-                listview.getPaddingRight(), mPaddingBottom);
-        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mOtherRowsCenterToBottom = getResources()
-                .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
-        mPaddingBottom =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
-        mBgDarkColor =
-                getResources().getColor(R.color.lb_playback_controls_background_dark);
-        mBgLightColor =
-                getResources().getColor(R.color.lb_playback_controls_background_light);
-        mShowTimeMs =
-                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
-        mMajorFadeTranslateY =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
-        mMinorFadeTranslateY =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
-
-        loadBgAnimator();
-        loadControlRowAnimator();
-        loadOtherRowAnimator();
-        loadDescriptionAnimator();
-    }
-
-    /**
-     * Sets the background type.
-     *
-     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
-     */
-    public void setBackgroundType(int type) {
-        switch (type) {
-        case BG_LIGHT:
-        case BG_DARK:
-        case BG_NONE:
-            if (type != mBackgroundType) {
-                mBackgroundType = type;
-                updateBackground();
-            }
-            break;
-        default:
-            throw new IllegalArgumentException("Invalid background type");
-        }
-    }
-
-    /**
-     * Returns the background type.
-     */
-    public int getBackgroundType() {
-        return mBackgroundType;
-    }
-
-    private void updateBackground() {
-        if (mRootView != null) {
-            int color = mBgDarkColor;
-            switch (mBackgroundType) {
-                case BG_DARK: break;
-                case BG_LIGHT: color = mBgLightColor; break;
-                case BG_NONE: color = Color.TRANSPARENT; break;
-            }
-            mRootView.setBackground(new ColorDrawable(color));
-        }
-    }
-
-    void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
-        // Add extra space between rows 0 and 1
-        if (vh == null && getVerticalGridView() != null) {
-            vh = (ItemBridgeAdapter.ViewHolder)
-                    getVerticalGridView().findViewHolderForPosition(0);
-        }
-        if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
-            final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
-            ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
-                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
-                    adapterSize > 1);
-        }
-    }
-
-    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
-            new ItemBridgeAdapter.AdapterListener() {
-        @Override
-        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
-            if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
-            if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
-                if (DEBUG) Log.v(TAG, "setting alpha to 0");
-                vh.getViewHolder().view.setAlpha(0);
-            }
-            if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
-                resetControlsToPrimaryActions(vh);
-            }
-        }
-        @Override
-        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
-            if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
-            // Reset animation state
-            vh.getViewHolder().view.setAlpha(1f);
-            vh.getViewHolder().view.setTranslationY(0);
-            if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
-                Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
-                        vh.getViewHolder()).mDescriptionViewHolder;
-                if (descriptionVh != null) {
-                    descriptionVh.view.setAlpha(1f);
-                }
-            }
-        }
-        @Override
-        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
-            if (vh.getPosition() == 0) {
-                updateControlsBottomSpace(vh);
-            }
-        }
-    };
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        mRootView = super.onCreateView(inflater, container, savedInstanceState);
-        mBgAlpha = 255;
-        updateBackground();
-        getRowsFragment().setExternalAdapterListener(mAdapterListener);
-        return mRootView;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mRootView = null;
-        if (mHostCallback != null) {
-            mHostCallback.onHostDestroy();
-        }
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        // Workaround problem VideoView forcing itself to focused, let controls take focus.
-        getRowsFragment().getView().requestFocus();
-        if (mHostCallback != null) {
-            mHostCallback.onHostStart();
-        }
-    }
-
-    private final DataObserver mObserver = new DataObserver() {
-        @Override
-        public void onChanged() {
-            updateControlsBottomSpace(null);
-        }
-    };
-
-    static abstract class AnimatorListener implements Animator.AnimatorListener {
-        ArrayList<View> mViews = new ArrayList<View>();
-        ArrayList<Integer> mLayerType = new ArrayList<Integer>();
-
-        @Override
-        public void onAnimationCancel(Animator animation) {
-        }
-        @Override
-        public void onAnimationRepeat(Animator animation) {
-        }
-        @Override
-        public void onAnimationStart(Animator animation) {
-            getViews(mViews);
-            for (View view : mViews) {
-                mLayerType.add(view.getLayerType());
-                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            }
-        }
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            for (int i = 0; i < mViews.size(); i++) {
-                mViews.get(i).setLayerType(mLayerType.get(i), null);
-            }
-            mLayerType.clear();
-            mViews.clear();
-        }
-        abstract void getViews(ArrayList<View> views);
-
-    };
-}
diff --git a/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
deleted file mode 100644
index d751320..0000000
--- a/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ /dev/null
@@ -1,866 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from PlaybackOverlayFragment.java.  DO NOT MODIFY. */
-
-/*
- * 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.v17.leanback.app;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.animation.LogAccelerateInterpolator;
-import android.support.v17.leanback.animation.LogDecelerateInterpolator;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.ItemAlignmentFacet;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * A fragment for displaying playback controls and related content.
- * <p>
- * A PlaybackOverlaySupportFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
- * of {@link RowPresenter}.
- * </p>
- * <p>
- * An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
- * at position 0 in the adapter.
- * </p>
- * <p>
- *  This class is now deprecated, please us
- * </p>
- * @deprecated Use {@link PlaybackSupportFragment}.
- */
-@Deprecated
-public class PlaybackOverlaySupportFragment extends DetailsSupportFragment {
-
-    /**
-     * No background.
-     */
-    public static final int BG_NONE = 0;
-
-    /**
-     * A dark translucent background.
-     */
-    public static final int BG_DARK = 1;
-
-    /**
-     * A light translucent background.
-     */
-    public static final int BG_LIGHT = 2;
-
-    /**
-     * Listener allowing the application to receive notification of fade in and/or fade out
-     * completion events.
-     */
-    public static class OnFadeCompleteListener {
-        public void onFadeInComplete() {
-        }
-        public void onFadeOutComplete() {
-        }
-    }
-
-    static final String TAG = "PlaybackOF";
-    static final boolean DEBUG = false;
-    private static final int ANIMATION_MULTIPLIER = 1;
-
-    static int START_FADE_OUT = 1;
-
-    // Fading status
-    static final int IDLE = 0;
-    private static final int IN = 1;
-    static final int OUT = 2;
-
-    private int mOtherRowsCenterToBottom;
-    private int mPaddingBottom;
-    private View mRootView;
-    private int mBackgroundType = BG_DARK;
-    private int mBgDarkColor;
-    private int mBgLightColor;
-    private int mShowTimeMs;
-    private int mMajorFadeTranslateY, mMinorFadeTranslateY;
-    int mAnimationTranslateY;
-    OnFadeCompleteListener mFadeCompleteListener;
-    private PlaybackControlGlue.InputEventHandler mInputEventHandler;
-    boolean mFadingEnabled = true;
-    int mFadingStatus = IDLE;
-    int mBgAlpha;
-    private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
-    private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
-    private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
-    private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
-    boolean mResetControlsToPrimaryActionsPending;
-    PlaybackGlueHost.HostCallback mHostCallback;
-
-    private final Animator.AnimatorListener mFadeListener =
-            new Animator.AnimatorListener() {
-        @Override
-        public void onAnimationStart(Animator animation) {
-            enableVerticalGridAnimations(false);
-        }
-        @Override
-        public void onAnimationRepeat(Animator animation) {
-        }
-        @Override
-        public void onAnimationCancel(Animator animation) {
-        }
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
-            if (mBgAlpha > 0) {
-                enableVerticalGridAnimations(true);
-                startFadeTimer();
-                if (mFadeCompleteListener != null) {
-                    mFadeCompleteListener.onFadeInComplete();
-                }
-            } else {
-                VerticalGridView verticalView = getVerticalGridView();
-                // reset focus to the primary actions only if the selected row was the controls row
-                if (verticalView != null && verticalView.getSelectedPosition() == 0) {
-                    resetControlsToPrimaryActions(null);
-                }
-                if (mFadeCompleteListener != null) {
-                    mFadeCompleteListener.onFadeOutComplete();
-                }
-            }
-            mFadingStatus = IDLE;
-        }
-    };
-
-    static class FadeHandler extends Handler {
-        @Override
-        public void handleMessage(Message message) {
-            PlaybackOverlaySupportFragment fragment;
-            if (message.what == START_FADE_OUT) {
-                fragment = ((WeakReference<PlaybackOverlaySupportFragment>) message.obj).get();
-                if (fragment != null && fragment.mFadingEnabled) {
-                    fragment.fade(false);
-                }
-            }
-        }
-    }
-
-    static final Handler sHandler = new FadeHandler();
-
-    final WeakReference<PlaybackOverlaySupportFragment> mFragmentReference =  new WeakReference(this);
-
-    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
-            new VerticalGridView.OnTouchInterceptListener() {
-        @Override
-        public boolean onInterceptTouchEvent(MotionEvent event) {
-            return onInterceptInputEvent(event);
-        }
-    };
-
-    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
-            new VerticalGridView.OnKeyInterceptListener() {
-        @Override
-        public boolean onInterceptKeyEvent(KeyEvent event) {
-            return onInterceptInputEvent(event);
-        }
-    };
-
-    void setBgAlpha(int alpha) {
-        mBgAlpha = alpha;
-        if (mRootView != null) {
-            mRootView.getBackground().setAlpha(alpha);
-        }
-    }
-
-    void enableVerticalGridAnimations(boolean enable) {
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().setAnimateChildLayout(enable);
-        }
-    }
-
-    void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
-        if (vh == null && getVerticalGridView() != null) {
-            vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
-        }
-        if (vh == null) {
-            mResetControlsToPrimaryActionsPending = true;
-        } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
-            mResetControlsToPrimaryActionsPending = false;
-            ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
-                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
-        }
-    }
-
-    /**
-     * Enables or disables view fading.  If enabled,
-     * the view will be faded in when the fragment starts,
-     * and will fade out after a time period.  The timeout
-     * period is reset each time {@link #tickle} is called.
-     *
-     */
-    public void setFadingEnabled(boolean enabled) {
-        if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
-        if (enabled != mFadingEnabled) {
-            mFadingEnabled = enabled;
-            if (mFadingEnabled) {
-                if (isResumed() && mFadingStatus == IDLE
-                        && !sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
-                    startFadeTimer();
-                }
-            } else {
-                // Ensure fully opaque
-                sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-                fade(true);
-            }
-        }
-    }
-
-    /**
-     * Returns true if view fading is enabled.
-     */
-    public boolean isFadingEnabled() {
-        return mFadingEnabled;
-    }
-
-    /**
-     * Sets the listener to be called when fade in or out has completed.
-     */
-    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
-        mFadeCompleteListener = listener;
-    }
-
-    /**
-     * Returns the listener to be called when fade in or out has completed.
-     */
-    public OnFadeCompleteListener getFadeCompleteListener() {
-        return mFadeCompleteListener;
-    }
-
-    @Deprecated
-    public interface InputEventHandler extends PlaybackControlGlue.InputEventHandler {
-    }
-
-    /**
-     * Sets the input event handler.
-     */
-    @Deprecated
-    public final void setInputEventHandler(InputEventHandler handler) {
-        mInputEventHandler = handler;
-    }
-
-    /**
-     * Returns the input event handler.
-     */
-    @Deprecated
-    public final InputEventHandler getInputEventHandler() {
-        return (InputEventHandler)mInputEventHandler;
-    }
-
-    /**
-     * Sets the input event handler.
-     */
-    public final void setEventHandler(PlaybackControlGlue.InputEventHandler handler) {
-        mInputEventHandler = handler;
-    }
-
-    /**
-     * Returns the input event handler.
-     */
-    public final PlaybackControlGlue.InputEventHandler getEventHandler() {
-        return mInputEventHandler;
-    }
-
-    /**
-     * Tickles the playback controls.  Fades in the view if it was faded out,
-     * otherwise resets the fade out timer.  Tickling on input events is handled
-     * by the fragment.
-     */
-    public void tickle() {
-        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
-        if (!mFadingEnabled || !isResumed()) {
-            return;
-        }
-        if (sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
-            // Restart the timer
-            startFadeTimer();
-        } else {
-            fade(true);
-        }
-    }
-
-    /**
-     * Fades out the playback overlay immediately.
-     */
-    public void fadeOut() {
-        sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-        fade(false);
-    }
-
-    /**
-     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
-     * take appropriate actions to take action when the hosting fragment starts/stops processing.
-     */
-    void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
-        this.mHostCallback = hostCallback;
-    }
-
-    @Override
-    public void onStop() {
-        if (mHostCallback != null) {
-            mHostCallback.onHostStop();
-        }
-        super.onStop();
-    }
-
-    @Override
-    public void onPause() {
-        if (mHostCallback != null) {
-            mHostCallback.onHostPause();
-        }
-        super.onPause();
-    }
-
-    private boolean areControlsHidden() {
-        return mFadingStatus == IDLE && mBgAlpha == 0;
-    }
-
-    boolean onInterceptInputEvent(InputEvent event) {
-        final boolean controlsHidden = areControlsHidden();
-        if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
-        boolean consumeEvent = false;
-        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
-
-        if (mInputEventHandler != null) {
-            consumeEvent = mInputEventHandler.handleInputEvent(event);
-        }
-        if (event instanceof KeyEvent) {
-            keyCode = ((KeyEvent) event).getKeyCode();
-        }
-
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                // Event may be consumed; regardless, if controls are hidden then these keys will
-                // bring up the controls.
-                if (controlsHidden) {
-                    consumeEvent = true;
-                }
-                tickle();
-                break;
-            case KeyEvent.KEYCODE_BACK:
-            case KeyEvent.KEYCODE_ESCAPE:
-                // If fading enabled and controls are not hidden, back will be consumed to fade
-                // them out (even if the key was consumed by the handler).
-                if (mFadingEnabled && !controlsHidden) {
-                    consumeEvent = true;
-                    sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-                    fade(false);
-                } else if (consumeEvent) {
-                    tickle();
-                }
-                break;
-            default:
-                if (consumeEvent) {
-                    tickle();
-                }
-        }
-        return consumeEvent;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mFadingEnabled) {
-            setBgAlpha(0);
-            fade(true);
-        }
-        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
-        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
-        if (mHostCallback != null) {
-            mHostCallback.onHostResume();
-        }
-    }
-
-    void startFadeTimer() {
-        sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
-        sHandler.sendMessageDelayed(sHandler.obtainMessage(START_FADE_OUT, mFragmentReference),
-                mShowTimeMs);
-    }
-
-    private static ValueAnimator loadAnimator(Context context, int resId) {
-        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
-        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
-        return animator;
-    }
-
-    private void loadBgAnimator() {
-        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                setBgAlpha((Integer) arg0.getAnimatedValue());
-            }
-        };
-
-        Context context = getContext();
-        mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
-        mBgFadeInAnimator.addUpdateListener(listener);
-        mBgFadeInAnimator.addListener(mFadeListener);
-
-        mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
-        mBgFadeOutAnimator.addUpdateListener(listener);
-        mBgFadeOutAnimator.addListener(mFadeListener);
-    }
-
-    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
-    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
-
-    View getControlRowView() {
-        if (getVerticalGridView() == null) {
-            return null;
-        }
-        RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
-        if (vh == null) {
-            return null;
-        }
-        return vh.itemView;
-    }
-
-    private void loadControlRowAnimator() {
-        final AnimatorListener listener = new AnimatorListener() {
-            @Override
-            void getViews(ArrayList<View> views) {
-                View view = getControlRowView();
-                if (view != null) {
-                    views.add(view);
-                }
-            }
-        };
-        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                View view = getControlRowView();
-                if (view != null) {
-                    final float fraction = (Float) arg0.getAnimatedValue();
-                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
-                    view.setAlpha(fraction);
-                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
-                }
-            }
-        };
-
-        Context context = getContext();
-        mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
-        mControlRowFadeInAnimator.addUpdateListener(updateListener);
-        mControlRowFadeInAnimator.addListener(listener);
-        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mControlRowFadeOutAnimator = loadAnimator(context,
-                R.animator.lb_playback_controls_fade_out);
-        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
-        mControlRowFadeOutAnimator.addListener(listener);
-        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
-    }
-
-    private void loadOtherRowAnimator() {
-        final AnimatorListener listener = new AnimatorListener() {
-            @Override
-            void getViews(ArrayList<View> views) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                final int count = getVerticalGridView().getChildCount();
-                for (int i = 0; i < count; i++) {
-                    View view = getVerticalGridView().getChildAt(i);
-                    if (view != null) {
-                        views.add(view);
-                    }
-                }
-            }
-        };
-        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                final float fraction = (Float) arg0.getAnimatedValue();
-                for (View view : listener.mViews) {
-                    if (getVerticalGridView().getChildPosition(view) > 0) {
-                        view.setAlpha(fraction);
-                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
-                    }
-                }
-            }
-        };
-
-        Context context = getContext();
-        mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
-        mOtherRowFadeInAnimator.addListener(listener);
-        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
-        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
-        mOtherRowFadeOutAnimator.addListener(listener);
-        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
-        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
-    }
-
-    private void loadDescriptionAnimator() {
-        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
-                        getVerticalGridView().findViewHolderForPosition(0);
-                if (adapterVh != null && adapterVh.getViewHolder()
-                        instanceof PlaybackControlsRowPresenter.ViewHolder) {
-                    final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
-                            adapterVh.getViewHolder()).mDescriptionViewHolder;
-                    if (vh != null) {
-                        vh.view.setAlpha((Float) arg0.getAnimatedValue());
-                    }
-                }
-            }
-        };
-
-        Context context = getContext();
-        mDescriptionFadeInAnimator = loadAnimator(context,
-                R.animator.lb_playback_description_fade_in);
-        mDescriptionFadeInAnimator.addUpdateListener(listener);
-        mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mDescriptionFadeOutAnimator = loadAnimator(context,
-                R.animator.lb_playback_description_fade_out);
-        mDescriptionFadeOutAnimator.addUpdateListener(listener);
-    }
-
-    void fade(boolean fadeIn) {
-        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
-        if (getView() == null) {
-            return;
-        }
-        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
-            if (DEBUG) Log.v(TAG, "requested fade in progress");
-            return;
-        }
-        if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
-            if (DEBUG) Log.v(TAG, "fade is no-op");
-            return;
-        }
-
-        mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0
-                ? mMajorFadeTranslateY : mMinorFadeTranslateY;
-
-        if (mFadingStatus == IDLE) {
-            if (fadeIn) {
-                mBgFadeInAnimator.start();
-                mControlRowFadeInAnimator.start();
-                mOtherRowFadeInAnimator.start();
-                mDescriptionFadeInAnimator.start();
-            } else {
-                mBgFadeOutAnimator.start();
-                mControlRowFadeOutAnimator.start();
-                mOtherRowFadeOutAnimator.start();
-                mDescriptionFadeOutAnimator.start();
-            }
-        } else {
-            if (fadeIn) {
-                mBgFadeOutAnimator.reverse();
-                mControlRowFadeOutAnimator.reverse();
-                mOtherRowFadeOutAnimator.reverse();
-                mDescriptionFadeOutAnimator.reverse();
-            } else {
-                mBgFadeInAnimator.reverse();
-                mControlRowFadeInAnimator.reverse();
-                mOtherRowFadeInAnimator.reverse();
-                mDescriptionFadeInAnimator.reverse();
-            }
-        }
-        getView().announceForAccessibility(getString(fadeIn ? R.string.lb_playback_controls_shown
-                : R.string.lb_playback_controls_hidden));
-
-        // If fading in while control row is focused, set initial translationY so
-        // views slide in from below.
-        if (fadeIn && mFadingStatus == IDLE) {
-            final int count = getVerticalGridView().getChildCount();
-            for (int i = 0; i < count; i++) {
-                getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
-            }
-        }
-
-        mFadingStatus = fadeIn ? IN : OUT;
-    }
-
-    /**
-     * Sets the list of rows for the fragment.
-     */
-    @Override
-    public void setAdapter(ObjectAdapter adapter) {
-        if (getAdapter() != null) {
-            getAdapter().unregisterObserver(mObserver);
-        }
-        super.setAdapter(adapter);
-        if (adapter != null) {
-            adapter.registerObserver(mObserver);
-        }
-    }
-
-    @Override
-    protected void setupPresenter(Presenter rowPresenter) {
-        if (rowPresenter instanceof PlaybackRowPresenter) {
-            if (rowPresenter.getFacet(ItemAlignmentFacet.class) == null) {
-                ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
-                ItemAlignmentFacet.ItemAlignmentDef def =
-                        new ItemAlignmentFacet.ItemAlignmentDef();
-                def.setItemAlignmentOffset(0);
-                def.setItemAlignmentOffsetPercent(100);
-                itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
-                        {def});
-                rowPresenter.setFacet(ItemAlignmentFacet.class, itemAlignment);
-            }
-        } else {
-            super.setupPresenter(rowPresenter);
-        }
-    }
-
-    @Override
-    void setVerticalGridViewLayout(VerticalGridView listview) {
-        if (listview == null) {
-            return;
-        }
-
-        // we set the base line of alignment to -paddingBottom
-        listview.setWindowAlignmentOffset(-mPaddingBottom);
-        listview.setWindowAlignmentOffsetPercent(
-                VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-
-        // align other rows that arent the last to center of screen, since our baseline is
-        // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
-        listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
-        listview.setItemAlignmentOffsetPercent(50);
-
-        // Push last row to the bottom padding
-        // Padding affects alignment when last row is focused
-        listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
-                listview.getPaddingRight(), mPaddingBottom);
-        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mOtherRowsCenterToBottom = getResources()
-                .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
-        mPaddingBottom =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
-        mBgDarkColor =
-                getResources().getColor(R.color.lb_playback_controls_background_dark);
-        mBgLightColor =
-                getResources().getColor(R.color.lb_playback_controls_background_light);
-        mShowTimeMs =
-                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
-        mMajorFadeTranslateY =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
-        mMinorFadeTranslateY =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
-
-        loadBgAnimator();
-        loadControlRowAnimator();
-        loadOtherRowAnimator();
-        loadDescriptionAnimator();
-    }
-
-    /**
-     * Sets the background type.
-     *
-     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
-     */
-    public void setBackgroundType(int type) {
-        switch (type) {
-        case BG_LIGHT:
-        case BG_DARK:
-        case BG_NONE:
-            if (type != mBackgroundType) {
-                mBackgroundType = type;
-                updateBackground();
-            }
-            break;
-        default:
-            throw new IllegalArgumentException("Invalid background type");
-        }
-    }
-
-    /**
-     * Returns the background type.
-     */
-    public int getBackgroundType() {
-        return mBackgroundType;
-    }
-
-    private void updateBackground() {
-        if (mRootView != null) {
-            int color = mBgDarkColor;
-            switch (mBackgroundType) {
-                case BG_DARK: break;
-                case BG_LIGHT: color = mBgLightColor; break;
-                case BG_NONE: color = Color.TRANSPARENT; break;
-            }
-            mRootView.setBackground(new ColorDrawable(color));
-        }
-    }
-
-    void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
-        // Add extra space between rows 0 and 1
-        if (vh == null && getVerticalGridView() != null) {
-            vh = (ItemBridgeAdapter.ViewHolder)
-                    getVerticalGridView().findViewHolderForPosition(0);
-        }
-        if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
-            final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
-            ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
-                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
-                    adapterSize > 1);
-        }
-    }
-
-    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
-            new ItemBridgeAdapter.AdapterListener() {
-        @Override
-        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
-            if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
-            if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
-                if (DEBUG) Log.v(TAG, "setting alpha to 0");
-                vh.getViewHolder().view.setAlpha(0);
-            }
-            if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
-                resetControlsToPrimaryActions(vh);
-            }
-        }
-        @Override
-        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
-            if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
-            // Reset animation state
-            vh.getViewHolder().view.setAlpha(1f);
-            vh.getViewHolder().view.setTranslationY(0);
-            if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
-                Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
-                        vh.getViewHolder()).mDescriptionViewHolder;
-                if (descriptionVh != null) {
-                    descriptionVh.view.setAlpha(1f);
-                }
-            }
-        }
-        @Override
-        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
-            if (vh.getPosition() == 0) {
-                updateControlsBottomSpace(vh);
-            }
-        }
-    };
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        mRootView = super.onCreateView(inflater, container, savedInstanceState);
-        mBgAlpha = 255;
-        updateBackground();
-        getRowsSupportFragment().setExternalAdapterListener(mAdapterListener);
-        return mRootView;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mRootView = null;
-        if (mHostCallback != null) {
-            mHostCallback.onHostDestroy();
-        }
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        // Workaround problem VideoView forcing itself to focused, let controls take focus.
-        getRowsSupportFragment().getView().requestFocus();
-        if (mHostCallback != null) {
-            mHostCallback.onHostStart();
-        }
-    }
-
-    private final DataObserver mObserver = new DataObserver() {
-        @Override
-        public void onChanged() {
-            updateControlsBottomSpace(null);
-        }
-    };
-
-    static abstract class AnimatorListener implements Animator.AnimatorListener {
-        ArrayList<View> mViews = new ArrayList<View>();
-        ArrayList<Integer> mLayerType = new ArrayList<Integer>();
-
-        @Override
-        public void onAnimationCancel(Animator animation) {
-        }
-        @Override
-        public void onAnimationRepeat(Animator animation) {
-        }
-        @Override
-        public void onAnimationStart(Animator animation) {
-            getViews(mViews);
-            for (View view : mViews) {
-                mLayerType.add(view.getLayerType());
-                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            }
-        }
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            for (int i = 0; i < mViews.size(); i++) {
-                mViews.get(i).setLayerType(mLayerType.get(i), null);
-            }
-            mLayerType.clear();
-            mViews.clear();
-        }
-        abstract void getViews(ArrayList<View> views);
-
-    };
-}
diff --git a/android/support/v17/leanback/app/PlaybackSupportFragment.java b/android/support/v17/leanback/app/PlaybackSupportFragment.java
index d63e72c..a8741ab 100644
--- a/android/support/v17/leanback/app/PlaybackSupportFragment.java
+++ b/android/support/v17/leanback/app/PlaybackSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from PlaybackFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
@@ -21,13 +18,14 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.support.v4.app.Fragment;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
@@ -48,6 +46,7 @@
 import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.view.InputEvent;
@@ -450,7 +449,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         // controls view are initially visible, make it invisible
         // if app has called hideControlsOverlay() before view created.
diff --git a/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
index cdf3f97..e745094 100644
--- a/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoPlaybackFragmentGlueHost.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/RowsFragment.java b/android/support/v17/leanback/app/RowsFragment.java
index dd0dbed..a008ad6 100644
--- a/android/support/v17/leanback/app/RowsFragment.java
+++ b/android/support/v17/leanback/app/RowsFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -16,6 +19,8 @@
 import android.animation.TimeAnimator;
 import android.animation.TimeAnimator.TimeListener;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
 import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -285,7 +290,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         if (DEBUG) Log.v(TAG, "onViewCreated");
         super.onViewCreated(view, savedInstanceState);
         // Align the top edge of child with id row_content.
@@ -625,6 +630,11 @@
 
     }
 
+    /**
+     * The adapter that RowsFragment implements
+     * BrowseFragment.MainFragmentRowsAdapter.
+     * @see #getMainFragmentRowsAdapter().
+     */
     public static class MainFragmentRowsAdapter
             extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
 
diff --git a/android/support/v17/leanback/app/RowsSupportFragment.java b/android/support/v17/leanback/app/RowsSupportFragment.java
index c00f78b..05e3813 100644
--- a/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from RowsFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -19,6 +16,8 @@
 import android.animation.TimeAnimator;
 import android.animation.TimeAnimator.TimeListener;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
 import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -288,7 +287,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         if (DEBUG) Log.v(TAG, "onViewCreated");
         super.onViewCreated(view, savedInstanceState);
         // Align the top edge of child with id row_content.
@@ -628,6 +627,11 @@
 
     }
 
+    /**
+     * The adapter that RowsSupportFragment implements
+     * BrowseSupportFragment.MainFragmentRowsAdapter.
+     * @see #getMainFragmentRowsAdapter().
+     */
     public static class MainFragmentRowsAdapter
             extends BrowseSupportFragment.MainFragmentRowsAdapter<RowsSupportFragment> {
 
diff --git a/android/support/v17/leanback/app/SearchFragment.java b/android/support/v17/leanback/app/SearchFragment.java
index acf4745..2154ff2 100644
--- a/android/support/v17/leanback/app/SearchFragment.java
+++ b/android/support/v17/leanback/app/SearchFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from SearchSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -16,7 +19,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
-import android.app.Fragment;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -35,6 +37,7 @@
 import android.support.v17.leanback.widget.SearchOrbView;
 import android.support.v17.leanback.widget.SpeechRecognitionCallback;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -52,12 +55,11 @@
  * into a {@link RowsFragment}, in the same way that they are in a {@link
  * BrowseFragment}.
  *
- * <p>If you do not supply a callback via
- * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
- * recognizer will be used for which your application will need to declare
+ * <p>A SpeechRecognizer object will be created for which your application will need to declare
  * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
  * the device version is >= 23, a permission dialog will show first time using speech recognition.
  * 0 will be used as requestCode in requestPermissions() call.
+ * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)} is deprecated.
  * </p>
  * <p>
  * Speech recognition is automatically started when fragment is created, but
@@ -393,7 +395,7 @@
         mIsPaused = false;
         if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
             mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(
-                    FragmentUtil.getContext(this));
+                    FragmentUtil.getContext(SearchFragment.this));
             mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
         }
         if (mPendingStartRecognitionWhenPaused) {
@@ -576,8 +578,11 @@
 
     /**
      * Sets this callback to have the fragment pass speech recognition requests
-     * to the activity rather than using an internal recognizer.
+     * to the activity rather than using a SpeechRecognizer object.
+     * @deprecated Launching voice recognition activity is no longer supported. App should declare
+     *             android.permission.RECORD_AUDIO in AndroidManifest file.
      */
+    @Deprecated
     public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
         mSpeechRecognitionCallback = callback;
         if (mSearchBar != null) {
diff --git a/android/support/v17/leanback/app/SearchSupportFragment.java b/android/support/v17/leanback/app/SearchSupportFragment.java
index 36b560d..ed2a679 100644
--- a/android/support/v17/leanback/app/SearchSupportFragment.java
+++ b/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from SearchFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -19,7 +16,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
-import android.support.v4.app.Fragment;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -38,6 +34,7 @@
 import android.support.v17.leanback.widget.SearchOrbView;
 import android.support.v17.leanback.widget.SpeechRecognitionCallback;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -55,12 +52,11 @@
  * into a {@link RowsSupportFragment}, in the same way that they are in a {@link
  * BrowseSupportFragment}.
  *
- * <p>If you do not supply a callback via
- * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
- * recognizer will be used for which your application will need to declare
+ * <p>A SpeechRecognizer object will be created for which your application will need to declare
  * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
  * the device version is >= 23, a permission dialog will show first time using speech recognition.
  * 0 will be used as requestCode in requestPermissions() call.
+ * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)} is deprecated.
  * </p>
  * <p>
  * Speech recognition is automatically started when fragment is created, but
@@ -579,8 +575,11 @@
 
     /**
      * Sets this callback to have the fragment pass speech recognition requests
-     * to the activity rather than using an internal recognizer.
+     * to the activity rather than using a SpeechRecognizer object.
+     * @deprecated Launching voice recognition activity is no longer supported. App should declare
+     *             android.permission.RECORD_AUDIO in AndroidManifest file.
      */
+    @Deprecated
     public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
         mSpeechRecognitionCallback = callback;
         if (mSearchBar != null) {
diff --git a/android/support/v17/leanback/app/VerticalGridFragment.java b/android/support/v17/leanback/app/VerticalGridFragment.java
index 5cf5799..5bc52ff 100644
--- a/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VerticalGridSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -240,7 +243,7 @@
 
     @Override
     protected Object createEntranceTransition() {
-        return TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+        return TransitionHelper.loadTransition(FragmentUtil.getContext(VerticalGridFragment.this),
                 R.transition.lb_vertical_grid_entrance_transition);
     }
 
diff --git a/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/android/support/v17/leanback/app/VerticalGridSupportFragment.java
index a38bac5..4cfe981 100644
--- a/android/support/v17/leanback/app/VerticalGridSupportFragment.java
+++ b/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VerticalGridFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2014 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/VideoFragment.java b/android/support/v17/leanback/app/VideoFragment.java
index 41241d0..1b2b8d0 100644
--- a/android/support/v17/leanback/app/VideoFragment.java
+++ b/android/support/v17/leanback/app/VideoFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VideoSupportFragment.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
@@ -38,7 +41,7 @@
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
-        mVideoSurface = (SurfaceView) LayoutInflater.from(FragmentUtil.getContext(this)).inflate(
+        mVideoSurface = (SurfaceView) LayoutInflater.from(FragmentUtil.getContext(VideoFragment.this)).inflate(
                 R.layout.lb_video_surface, root, false);
         root.addView(mVideoSurface, 0);
         mVideoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
diff --git a/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
index a64b521..d123676 100644
--- a/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VideoSupportFragmentGlueHost.java.  DO NOT MODIFY. */
+
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/VideoSupportFragment.java b/android/support/v17/leanback/app/VideoSupportFragment.java
index 321bdbe..51003d3 100644
--- a/android/support/v17/leanback/app/VideoSupportFragment.java
+++ b/android/support/v17/leanback/app/VideoSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoFragment.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java b/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
index 28f919b..66aabc4 100644
--- a/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoVideoFragmentGlueHost.java.  DO NOT MODIFY. */
-
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/android/support/v17/leanback/app/package-info.java b/android/support/v17/leanback/app/package-info.java
index 852a007..b736909 100644
--- a/android/support/v17/leanback/app/package-info.java
+++ b/android/support/v17/leanback/app/package-info.java
@@ -13,47 +13,54 @@
  */
 
 /**
- * <p>Support classes providing high level Leanback user interface building blocks:
- * fragments and helpers.</p>
+ * <p>Support classes providing high level Leanback user interface building blocks.</p>
  * <p>
- * Leanback fragments are available both as platform fragments (subclassed from
- * {@link android.app.Fragment android.app.Fragment}) and as support fragments (subclassed from
- * {@link android.support.v4.app.Fragment android.support.v4.app.Fragment}).  A few of the most
+ * Leanback fragments are available both as support fragments (subclassed from
+ * {@link android.support.v4.app.Fragment android.support.v4.app.Fragment}) and as platform
+ * fragments (subclassed from {@link android.app.Fragment android.app.Fragment}). A few of the most
  * commonly used leanback fragments are described here.
  * </p>
  * <p>
- * A {@link android.support.v17.leanback.app.BrowseFragment} includes an optional “fastlane”
+ * A {@link android.support.v17.leanback.app.BrowseSupportFragment} by default operates in the "row"
+ * mode. It includes an optional “fastlane”
  * navigation side panel and a list of rows, with one-to-one correspondance between each header
  * in the fastlane and a row.  The application supplies the
  * {@link android.support.v17.leanback.widget.ObjectAdapter} containing the list of
  * rows and a {@link android.support.v17.leanback.widget.PresenterSelector} of row presenters.
  * </p>
  * <p>
- * A {@link android.support.v17.leanback.app.DetailsFragment} will typically consist of a large
- * overview of an item at the top,
+ * A {@link android.support.v17.leanback.app.BrowseSupportFragment} also works in a "page" mode when
+ * each row of fastlane is mapped to a fragment that the app registers in
+ * {@link android.support.v17.leanback.app.BrowseSupportFragment#getMainFragmentRegistry()}.
+ * </p>
+ * <p>
+ * A {@link android.support.v17.leanback.app.DetailsSupportFragment} will typically consist of a
+ * large overview of an item at the top,
  * some actions that a user can perform, and possibly rows of additional or related items.
- * The content for this fragment is specified in the same way as for the BrowseFragment, with the
- * convention that the first element in the ObjectAdapter corresponds to the overview row.
+ * The content for this fragment is specified in the same way as for the BrowseSupportFragment, with
+ * the convention that the first element in the ObjectAdapter corresponds to the overview row.
  * The {@link android.support.v17.leanback.widget.DetailsOverviewRow} and
- * {@link android.support.v17.leanback.widget.DetailsOverviewRowPresenter} provide a default template
- * for this row.
+ * {@link android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter} provide a
+ * default template for this row.
  * </p>
  * <p>
- * A {@link android.support.v17.leanback.app.PlaybackOverlayFragment} implements standard playback
- * transport controls with a Leanback
- * look and feel.  It is recommended to use an instance of the
- * {@link android.support.v17.leanback.app.PlaybackControlGlue} with the
- * PlaybackOverlayFragment.  This helper implements a standard behavior for user interaction with
- * the most commonly used controls such as fast forward and rewind.
+ * A {@link android.support.v17.leanback.app.PlaybackSupportFragment} or its subclass
+ * {@link android.support.v17.leanback.app.VideoSupportFragment} hosts
+ * {@link android.support.v17.leanback.media.PlaybackTransportControlGlue}
+ * or {@link android.support.v17.leanback.media.PlaybackBannerControlGlue} with a Leanback
+ * look and feel.  It is recommended to use an instance of
+ * {@link android.support.v17.leanback.media.PlaybackTransportControlGlue}.
+ * This helper implements a standard behavior for user interaction with
+ * the most commonly used controls as well as video scrubbing.
  * </p>
  * <p>
- * A {@link android.support.v17.leanback.app.SearchFragment} allows the developer to accept a query
- * from a user and display the results
+ * A {@link android.support.v17.leanback.app.SearchSupportFragment} allows the developer to accept a
+ * query from a user and display the results
  * using the familiar list rows.
  * </p>
  * <p>
- * A {@link android.support.v17.leanback.app.GuidedStepFragment} is used to guide the user through a
- * decision or series of decisions.
+ * A {@link android.support.v17.leanback.app.GuidedStepSupportFragment} is used to guide the user
+ * through a decision or series of decisions.
  * </p>
  **/
 
diff --git a/android/support/v17/leanback/media/MediaControllerGlue.java b/android/support/v17/leanback/media/MediaControllerGlue.java
index 730bf3a..b8e9b74 100644
--- a/android/support/v17/leanback/media/MediaControllerGlue.java
+++ b/android/support/v17/leanback/media/MediaControllerGlue.java
@@ -28,7 +28,10 @@
 
 /**
  * A helper class for implementing a glue layer for {@link MediaControllerCompat}.
+ * @deprecated Use {@link MediaControllerAdapter} with {@link PlaybackTransportControlGlue} or
+ *             {@link PlaybackBannerControlGlue}.
  */
+@Deprecated
 public abstract class MediaControllerGlue extends PlaybackControlGlue {
     static final String TAG = "MediaControllerGlue";
     static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/media/MediaPlayerGlue.java b/android/support/v17/leanback/media/MediaPlayerGlue.java
index 3a274b1..73bca97 100644
--- a/android/support/v17/leanback/media/MediaPlayerGlue.java
+++ b/android/support/v17/leanback/media/MediaPlayerGlue.java
@@ -22,6 +22,7 @@
 import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Handler;
+import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.OnItemViewSelectedListener;
@@ -50,7 +51,11 @@
  * </ul>
  *
  * @hide
+ * @deprecated Use {@link MediaPlayerAdapter} with {@link PlaybackTransportControlGlue} or
+ *             {@link PlaybackBannerControlGlue}.
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Deprecated
 public class MediaPlayerGlue extends PlaybackControlGlue implements
         OnItemViewSelectedListener {
 
@@ -485,11 +490,6 @@
     }
 
     @Override
-    public boolean isReadyForPlayback() {
-        return mInitialized;
-    }
-
-    @Override
     public boolean isPrepared() {
         return mInitialized;
     }
diff --git a/android/support/v17/leanback/media/PlaybackBannerControlGlue.java b/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
index ca424a8..e644632 100644
--- a/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
+++ b/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
@@ -59,6 +59,24 @@
  * {@link #onPlayCompleted()}.
  * </p>
  *
+ * Sample Code:
+ * <pre><code>
+ * public class MyVideoFragment extends VideoFragment {
+ *     &#64;Override
+ *     public void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *         PlaybackBannerControlGlue<MediaPlayerAdapter> playerGlue =
+ *                 new PlaybackBannerControlGlue(getActivity(),
+ *                         new MediaPlayerAdapter(getActivity()));
+ *         playerGlue.setHost(new VideoFragmentGlueHost(this));
+ *         playerGlue.setSubtitle("Leanback artist");
+ *         playerGlue.setTitle("Leanback team at work");
+ *         String uriPath = "android.resource://com.example.android.leanback/raw/video";
+ *         playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
+ *         playerGlue.playWhenPrepared();
+ *     }
+ * }
+ * </code></pre>
  * @param <T> Type of {@link PlayerAdapter} passed in constructor.
  */
 public class PlaybackBannerControlGlue<T extends PlayerAdapter>
diff --git a/android/support/v17/leanback/media/PlaybackGlue.java b/android/support/v17/leanback/media/PlaybackGlue.java
index 32d5545..7c59573 100644
--- a/android/support/v17/leanback/media/PlaybackGlue.java
+++ b/android/support/v17/leanback/media/PlaybackGlue.java
@@ -49,21 +49,10 @@
      */
     public abstract static class PlayerCallback {
         /**
-         * This method is fired when media is ready for playback {@link #isPrepared()}.
-         * @deprecated use {@link #onPreparedStateChanged(PlaybackGlue)}.
-         */
-        @Deprecated
-        public void onReadyForPlayback() {
-        }
-
-        /**
          * Event for {@link #isPrepared()} changed.
          * @param glue The PlaybackGlue that has changed {@link #isPrepared()}.
          */
         public void onPreparedStateChanged(PlaybackGlue glue) {
-            if (glue.isPrepared()) {
-                onReadyForPlayback();
-            }
         }
 
         /**
@@ -98,41 +87,12 @@
     }
 
     /**
-     * Returns true when the media player is ready to start media playback. Subclasses must
-     * implement this method correctly. When returning false, app may listen to
-     * {@link PlayerCallback#onReadyForPlayback()} event.
-     *
-     * @see PlayerCallback#onReadyForPlayback()
-     * @deprecated Use isPrepared() instead.
-     */
-    @Deprecated
-    public boolean isReadyForPlayback() {
-        return true;
-    }
-
-    /**
      * Returns true when the media player is prepared to start media playback. When returning false,
      * app may listen to {@link PlayerCallback#onPreparedStateChanged(PlaybackGlue)} event.
      * @return True if prepared, false otherwise.
      */
     public boolean isPrepared() {
-        return isReadyForPlayback();
-    }
-
-    /**
-     * Sets the {@link PlayerCallback} callback. It will reset the existing callbacks.
-     * In most cases you would call {@link #addPlayerCallback(PlayerCallback)}.
-     * @deprecated Use {@link #addPlayerCallback(PlayerCallback)}.
-     */
-    @Deprecated
-    public void setPlayerCallback(PlayerCallback playerCallback) {
-        if (playerCallback == null) {
-            if (mPlayerCallbacks != null) {
-                mPlayerCallbacks.clear();
-            }
-        } else {
-            addPlayerCallback(playerCallback);
-        }
+        return true;
     }
 
     /**
@@ -174,12 +134,32 @@
     }
 
     /**
-     * Starts the media player.
+     * Starts the media player. Does nothing if {@link #isPrepared()} is false. To wait
+     * {@link #isPrepared()} to be true before playing, use {@link #playWhenPrepared()}.
      */
     public void play() {
     }
 
     /**
+     * Starts play when {@link #isPrepared()} becomes true.
+     */
+    public void playWhenPrepared() {
+        if (isPrepared()) {
+            play();
+        } else {
+            addPlayerCallback(new PlayerCallback() {
+                @Override
+                public void onPreparedStateChanged(PlaybackGlue glue) {
+                    if (glue.isPrepared()) {
+                        removePlayerCallback(this);
+                        play();
+                    }
+                }
+            });
+        }
+    }
+
+    /**
      * Pauses the media player.
      */
     public void pause() {
diff --git a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
index 61ea52b..4aa9bf6 100644
--- a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
+++ b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
@@ -68,23 +68,15 @@
  *     &#64;Override
  *     public void onCreate(Bundle savedInstanceState) {
  *         super.onCreate(savedInstanceState);
- *         final PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
+ *         PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
  *                 new PlaybackTransportControlGlue(getActivity(),
  *                         new MediaPlayerAdapter(getActivity()));
  *         playerGlue.setHost(new VideoFragmentGlueHost(this));
- *         playerGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
- *             &#64;Override
- *             public void onPreparedStateChanged(PlaybackGlue glue) {
- *                 if (glue.isPrepared()) {
- *                     playerGlue.setSeekProvider(new MySeekProvider());
- *                     playerGlue.play();
- *                 }
- *             }
- *         });
  *         playerGlue.setSubtitle("Leanback artist");
  *         playerGlue.setTitle("Leanback team at work");
  *         String uriPath = "android.resource://com.example.android.leanback/raw/video";
  *         playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
+ *         playerGlue.playWhenPrepared();
  *     }
  * }
  * </code></pre>
diff --git a/android/support/v17/leanback/package-info.java b/android/support/v17/leanback/package-info.java
index aa64827..80b26e9 100644
--- a/android/support/v17/leanback/package-info.java
+++ b/android/support/v17/leanback/package-info.java
@@ -41,19 +41,20 @@
  * <p>
  * Leanback contains a mixture of higher level building blocks such as Fragments in the
  * {@link android.support.v17.leanback.app} package. Notable examples are the
- * {@link android.support.v17.leanback.app.BrowseFragment} and the
- * {@link android.support.v17.leanback.app.GuidedStepFragment}.  Helper classes are also provided
- * that work with the leanback fragments, for example the
- * {@link android.support.v17.leanback.app.PlaybackControlGlue}.
+ * {@link android.support.v17.leanback.app.BrowseSupportFragment},
+ * {@link android.support.v17.leanback.app.DetailsSupportFragment},
+ * {@link android.support.v17.leanback.app.PlaybackSupportFragment} and the
+ * {@link android.support.v17.leanback.app.GuidedStepSupportFragment}.  Helper classes are also
+ * provided that work with the leanback fragments, for example the
+ * {@link android.support.v17.leanback.media.PlaybackTransportControlGlue} and
+ * {@link android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost}.
  * </p>
  * <p>
  * Many lower level building blocks are also provided in the {@link android.support.v17.leanback.widget} package.
  * These allow applications to easily incorporate Leanback look and feel while allowing for a
  * high degree of customization.  Primary examples include the UI widget
  * {@link android.support.v17.leanback.widget.HorizontalGridView} and
- * {@link android.support.v17.leanback.widget.VerticalGridView}.  Helper classes also exist at this level
- * which do not depend on the leanback fragments, for example the
- * {@link android.support.v17.leanback.widget.TitleHelper}.
+ * {@link android.support.v17.leanback.widget.VerticalGridView}.
  */
 
 package android.support.v17.leanback;
\ No newline at end of file
diff --git a/android/support/v17/leanback/widget/ActionPresenterSelector.java b/android/support/v17/leanback/widget/ActionPresenterSelector.java
index 1ced4d4..a018c2e 100644
--- a/android/support/v17/leanback/widget/ActionPresenterSelector.java
+++ b/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -55,43 +55,13 @@
         }
     }
 
-    static class OneLineActionPresenter extends Presenter {
-        @Override
-        public ViewHolder onCreateViewHolder(ViewGroup parent) {
-            View v = LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.lb_action_1_line, parent, false);
-            return new ActionViewHolder(v, parent.getLayoutDirection());
-        }
-
+    abstract static class ActionPresenter extends Presenter {
         @Override
         public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
             Action action = (Action) item;
             ActionViewHolder vh = (ActionViewHolder) viewHolder;
             vh.mAction = action;
-            vh.mButton.setText(action.getLabel1());
-        }
-
-        @Override
-        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
-            ((ActionViewHolder) viewHolder).mAction = null;
-        }
-    }
-
-    static class TwoLineActionPresenter extends Presenter {
-        @Override
-        public ViewHolder onCreateViewHolder(ViewGroup parent) {
-            View v = LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.lb_action_2_lines, parent, false);
-            return new ActionViewHolder(v, parent.getLayoutDirection());
-        }
-
-        @Override
-        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
-            Action action = (Action) item;
-            ActionViewHolder vh = (ActionViewHolder) viewHolder;
             Drawable icon = action.getIcon();
-            vh.mAction = action;
-
             if (icon != null) {
                 final int startPadding = vh.view.getResources()
                         .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start);
@@ -108,6 +78,47 @@
             } else {
                 vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
             }
+        }
+
+        @Override
+        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+            vh.view.setPadding(0, 0, 0, 0);
+            vh.mAction = null;
+        }
+    }
+
+    static class OneLineActionPresenter extends ActionPresenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.lb_action_1_line, parent, false);
+            return new ActionViewHolder(v, parent.getLayoutDirection());
+        }
+
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            super.onBindViewHolder(viewHolder, item);
+            ActionViewHolder vh = ((ActionViewHolder) viewHolder);
+            Action action = (Action) item;
+            vh.mButton.setText(action.getLabel1());
+        }
+    }
+
+    static class TwoLineActionPresenter extends ActionPresenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.lb_action_2_lines, parent, false);
+            return new ActionViewHolder(v, parent.getLayoutDirection());
+        }
+
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            super.onBindViewHolder(viewHolder, item);
+            Action action = (Action) item;
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
 
             CharSequence line1 = action.getLabel1();
             CharSequence line2 = action.getLabel2();
@@ -119,13 +130,5 @@
                 vh.mButton.setText(line1 + "\n" + line2);
             }
         }
-
-        @Override
-        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
-            ActionViewHolder vh = (ActionViewHolder) viewHolder;
-            vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
-            vh.view.setPadding(0, 0, 0, 0);
-            vh.mAction = null;
-        }
     }
 }
diff --git a/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 88de24c..00bc073 100644
--- a/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -230,10 +230,17 @@
      * specified position.
      *
      * @param itemList List of new Items
-     * @param callback DiffCallback Object to compute the difference between the old data set and
-     *                 new data set.
+     * @param callback Optional DiffCallback Object to compute the difference between the old data
+     *                 set and new data set. When null, {@link #notifyChanged()} will be fired.
      */
     public void setItems(final List itemList, final DiffCallback callback) {
+        if (callback == null) {
+            // shortcut when DiffCallback is not provided
+            mItems.clear();
+            mItems.addAll(itemList);
+            notifyChanged();
+            return;
+        }
         mOldItems.clear();
         mOldItems.addAll(mItems);
 
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index 8b0c4a7..8143197 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2348,21 +2348,22 @@
     // scroll in main direction may add/prune views
     private int scrollDirectionPrimary(int da) {
         if (TRACE) TraceCompat.beginSection("scrollPrimary");
-        boolean isMaxUnknown = false, isMinUnknown = false;
-        int minScroll = 0, maxScroll = 0;
-        if (!mIsSlidingChildViews) {
+        // We apply the cap of maxScroll/minScroll to the delta, except for two cases:
+        // 1. when children are in sliding out mode
+        // 2. During onLayoutChildren(), it may compensate the remaining scroll delta,
+        //    we should honor the request regardless if it goes over minScroll / maxScroll.
+        //    (see b/64931938 testScrollAndRemove and testScrollAndRemoveSample1)
+        if (!mIsSlidingChildViews && !mInLayout) {
             if (da > 0) {
-                isMaxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
-                if (!isMaxUnknown) {
-                    maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
+                if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
+                    int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
                     if (da > maxScroll) {
                         da = maxScroll;
                     }
                 }
             } else if (da < 0) {
-                isMinUnknown = mWindowAlignment.mainAxis().isMinUnknown();
-                if (!isMinUnknown) {
-                    minScroll = mWindowAlignment.mainAxis().getMinScroll();
+                if (!mWindowAlignment.mainAxis().isMinUnknown()) {
+                    int minScroll = mWindowAlignment.mainAxis().getMinScroll();
                     if (da < minScroll) {
                         da = minScroll;
                     }
@@ -2856,7 +2857,8 @@
         if (!mScrollEnabled && smooth) {
             return;
         }
-        if (getScrollPosition(view, childView, sTwoInts)) {
+        if (getScrollPosition(view, childView, sTwoInts)
+                || extraDelta != 0 || extraDeltaSecondary != 0) {
             scrollGrid(sTwoInts[0] + extraDelta, sTwoInts[1] + extraDeltaSecondary, smooth);
         }
     }
diff --git a/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java b/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
index 82cfa79..000db3c 100644
--- a/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
+++ b/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
@@ -32,7 +32,7 @@
 /**
  * A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a
  * series of playback control buttons. Typically this row will be the first row in a fragment
- * such as the {@link android.support.v17.leanback.app.PlaybackOverlayFragment}.
+ * such as the {@link android.support.v17.leanback.app.PlaybackFragment}.
  *
  * <p>The detailed description is rendered using a {@link Presenter} passed in
  * {@link #PlaybackControlsRowPresenter(Presenter)}.  Typically this will be an instance of
diff --git a/android/support/v17/leanback/widget/SearchBar.java b/android/support/v17/leanback/widget/SearchBar.java
index 18f608e..1094343 100644
--- a/android/support/v17/leanback/widget/SearchBar.java
+++ b/android/support/v17/leanback/widget/SearchBar.java
@@ -116,14 +116,6 @@
 
     }
 
-    private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener =
-            new AudioManager.OnAudioFocusChangeListener() {
-                @Override
-                public void onAudioFocusChange(int focusChange) {
-                    stopRecognition();
-                }
-            };
-
     SearchBarListener mSearchBarListener;
     SearchEditText mSearchTextEditor;
     SpeechOrbView mSpeechOrbView;
@@ -495,7 +487,12 @@
 
     /**
      * Sets the speech recognition callback.
+     *
+     * @deprecated Launching voice recognition activity is no longer supported. App should declare
+     *             android.permission.RECORD_AUDIO in AndroidManifest file. See details in
+     *             {@link android.support.v17.leanback.app.SearchSupportFragment}.
      */
+    @Deprecated
     public void setSpeechRecognitionCallback(SpeechRecognitionCallback request) {
         mSpeechRecognitionCallback = request;
         if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) {
@@ -582,7 +579,6 @@
         if (mListening) {
             mSpeechRecognizer.cancel();
             mListening = false;
-            mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
         }
 
         mSpeechRecognizer.setRecognitionListener(null);
@@ -624,17 +620,6 @@
         }
 
         mRecognizing = true;
-        // Request audio focus
-        int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
-                // Use the music stream.
-                AudioManager.STREAM_MUSIC,
-                // Request exclusive transient focus.
-                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-
-
-        if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-            Log.w(TAG, "Could not get audio focus");
-        }
 
         mSearchTextEditor.setText("");
 
diff --git a/android/support/v17/leanback/widget/SpeechRecognitionCallback.java b/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
index 02b0990..173444d 100644
--- a/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
+++ b/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
@@ -15,7 +15,12 @@
 
 /**
  * Interface for receiving notification that speech recognition should be initiated.
+ *
+ * @deprecated Launching voice recognition activity is no longer supported. App should declare
+ *             android.permission.RECORD_AUDIO in AndroidManifest file. See details in
+ *             {@link android.support.v17.leanback.app.SearchSupportFragment}.
  */
+@Deprecated
 public interface SpeechRecognitionCallback {
     /**
      * Method invoked when speech recognition should be initiated.
diff --git a/android/support/v17/leanback/widget/WindowAlignment.java b/android/support/v17/leanback/widget/WindowAlignment.java
index 3ddb6f0..55fa758 100644
--- a/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/android/support/v17/leanback/widget/WindowAlignment.java
@@ -261,20 +261,18 @@
                             // minScroll
                             mMinScroll = Math.min(mMinScroll,
                                     calculateScrollToKeyLine(maxChildViewCenter, keyLine));
-                        } else {
-                            // don't over scroll max
-                            mMaxScroll = Math.max(mMinScroll, mMaxScroll);
                         }
+                        // don't over scroll max
+                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
                     } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
                         if (isPreferKeylineOverHighEdge()) {
                             // if we prefer key line, might align min child to key line for
                             // maxScroll
                             mMaxScroll = Math.max(mMaxScroll,
                                     calculateScrollToKeyLine(minChildViewCenter, keyLine));
-                        } else {
-                            // don't over scroll min
-                            mMinScroll = Math.min(mMinScroll, mMaxScroll);
                         }
+                        // don't over scroll min
+                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
                     }
                 } else {
                     if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
@@ -283,20 +281,18 @@
                             // maxScroll
                             mMaxScroll = Math.max(mMaxScroll,
                                     calculateScrollToKeyLine(minChildViewCenter, keyLine));
-                        } else {
-                            // don't over scroll min
-                            mMinScroll = Math.min(mMinScroll, mMaxScroll);
                         }
+                        // don't over scroll min
+                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
                     } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
                         if (isPreferKeylineOverHighEdge()) {
                             // if we prefer key line, might align max child to key line for
                             // minScroll
                             mMinScroll = Math.min(mMinScroll,
                                     calculateScrollToKeyLine(maxChildViewCenter, keyLine));
-                        } else {
-                            // don't over scroll max
-                            mMaxScroll = Math.max(mMinScroll, mMaxScroll);
                         }
+                        // don't over scroll max
+                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
                     }
                 }
             }
diff --git a/android/support/v17/preference/LeanbackPreferenceFragment.java b/android/support/v17/preference/LeanbackPreferenceFragment.java
index dbff1c8..48d14b8 100644
--- a/android/support/v17/preference/LeanbackPreferenceFragment.java
+++ b/android/support/v17/preference/LeanbackPreferenceFragment.java
@@ -30,12 +30,12 @@
  * <p>The following sample code shows a simple leanback preference fragment that is
  * populated from a resource.  The resource it loads is:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
  *
  * <p>The fragment needs only to implement {@link #onCreatePreferences(Bundle, String)} to populate
  * the list of preference objects:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
  *      support_fragment_leanback}
  */
 public abstract class LeanbackPreferenceFragment extends BaseLeanbackPreferenceFragment {
diff --git a/android/support/v17/preference/LeanbackSettingsFragment.java b/android/support/v17/preference/LeanbackSettingsFragment.java
index 08f19c4..d56a2a6 100644
--- a/android/support/v17/preference/LeanbackSettingsFragment.java
+++ b/android/support/v17/preference/LeanbackSettingsFragment.java
@@ -42,14 +42,14 @@
  * <p>The following sample code shows a simple leanback preference fragment that is
  * populated from a resource.  The resource it loads is:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
  *
  * <p>The sample implements
  * {@link PreferenceFragment.OnPreferenceStartFragmentCallback#onPreferenceStartFragment(PreferenceFragment, Preference)},
  * {@link PreferenceFragment.OnPreferenceStartScreenCallback#onPreferenceStartScreen(PreferenceFragment, PreferenceScreen)},
  * and {@link #onPreferenceStartInitialScreen()}:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
  *      support_fragment_leanback}
  */
 public abstract class LeanbackSettingsFragment extends Fragment
diff --git a/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java b/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
index 3905ca5..0dcd902 100644
--- a/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
+++ b/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
@@ -20,6 +20,8 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.view.View;
 
 /**
@@ -188,8 +190,9 @@
      * @param packageManager The current package manager
      * @return The localized description.
      */
+    @Nullable
     public static String loadDescription(
-            AccessibilityServiceInfo info, PackageManager packageManager) {
+            @NonNull AccessibilityServiceInfo info, @NonNull PackageManager packageManager) {
         if (Build.VERSION.SDK_INT >= 16) {
             return info.loadDescription(packageManager);
         } else {
@@ -206,6 +209,7 @@
      * @param feedbackType The feedback type.
      * @return The string representation.
      */
+    @NonNull
     public static String feedbackTypeToString(int feedbackType) {
         StringBuilder builder = new StringBuilder();
         builder.append("[");
@@ -245,6 +249,7 @@
      * @param flag The flag.
      * @return The string representation.
      */
+    @Nullable
     public static String flagToString(int flag) {
         switch (flag) {
             case AccessibilityServiceInfo.DEFAULT:
@@ -276,7 +281,7 @@
      * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
      * @see #CAPABILITY_CAN_FILTER_KEY_EVENTS
      */
-    public static int getCapabilities(AccessibilityServiceInfo info) {
+    public static int getCapabilities(@NonNull AccessibilityServiceInfo info) {
         if (Build.VERSION.SDK_INT >= 18) {
             return info.getCapabilities();
         } else {
@@ -296,6 +301,7 @@
      * @param capability The capability.
      * @return The string representation.
      */
+    @NonNull
     public static String capabilityToString(int capability) {
         switch (capability) {
             case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT:
diff --git a/android/support/v4/app/ActivityCompat.java b/android/support/v4/app/ActivityCompat.java
index f260508..5833481 100644
--- a/android/support/v4/app/ActivityCompat.java
+++ b/android/support/v4/app/ActivityCompat.java
@@ -74,6 +74,61 @@
     }
 
     /**
+     * Customizable delegate that allows delegating permission compatibility methods to a custom
+     * implementation.
+     *
+     * <p>
+     *     To delegate permission compatibility methods to a custom class, implement this interface,
+     *     and call {@code ActivityCompat.setPermissionCompatDelegate(delegate);}. All future calls
+     *     to the permission compatibility methods in this class will first check whether the
+     *     delegate can handle the method call, and invoke the corresponding method if it can.
+     * </p>
+     */
+    public interface PermissionCompatDelegate {
+
+        /**
+         * Determines whether the delegate should handle
+         * {@link ActivityCompat#requestPermissions(Activity, String[], int)}, and request
+         * permissions if applicable. If this method returns true, it means that permission
+         * request is successfully handled by the delegate, and platform should not perform any
+         * further requests for permission.
+         *
+         * @param activity The target activity.
+         * @param permissions The requested permissions. Must me non-null and not empty.
+         * @param requestCode Application specific request code to match with a result reported to
+         *    {@link
+         *    OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}.
+         *    Should be >= 0.
+         *
+         * @return Whether the delegate has handled the permission request.
+         * @see ActivityCompat#requestPermissions(Activity, String[], int)
+         */
+        boolean requestPermissions(@NonNull Activity activity,
+                @NonNull String[] permissions, @IntRange(from = 0) int requestCode);
+
+        /**
+         * Determines whether the delegate should handle the permission request as part of
+         * {@code FragmentActivity#onActivityResult(int, int, Intent)}. If this method returns true,
+         * it means that activity result is successfully handled by the delegate, and no further
+         * action is needed on this activity result.
+         *
+         * @param activity    The target Activity.
+         * @param requestCode The integer request code originally supplied to
+         *                    {@code startActivityForResult()}, allowing you to identify who this
+         *                    result came from.
+         * @param resultCode  The integer result code returned by the child activity
+         *                    through its {@code }setResult()}.
+         * @param data        An Intent, which can return result data to the caller
+         *                    (various data can be attached to Intent "extras").
+         *
+         * @return Whether the delegate has handled the activity result.
+         * @see ActivityCompat#requestPermissions(Activity, String[], int)
+         */
+        boolean onActivityResult(@NonNull Activity activity,
+                @IntRange(from = 0) int requestCode, int resultCode, @Nullable Intent data);
+    }
+
+    /**
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -81,6 +136,8 @@
         void validateRequestPermissionsRequestCode(int requestCode);
     }
 
+    private static PermissionCompatDelegate sDelegate;
+
     /**
      * This class should not be instantiated, but the constructor must be
      * visible for the class to be extended (as in support-v13).
@@ -90,6 +147,25 @@
     }
 
     /**
+     * Sets the permission delegate for {@code ActivityCompat}. Replaces the previously set
+     * delegate.
+     *
+     * @param delegate The delegate to be set. {@code null} to clear the set delegate.
+     */
+    public static void setPermissionCompatDelegate(
+            @Nullable PermissionCompatDelegate delegate) {
+        sDelegate = delegate;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static PermissionCompatDelegate getPermissionCompatDelegate() {
+        return sDelegate;
+    }
+
+    /**
      * Invalidate the activity's options menu, if able.
      *
      * <p>Before API level 11 (Android 3.0/Honeycomb) the lifecycle of the
@@ -120,7 +196,9 @@
      *
      * @param activity Invalidate the options menu of this activity
      * @return true if this operation was supported and it completed; false if it was not available.
+     * @deprecated Use {@link Activity#invalidateOptionsMenu()} directly.
      */
+    @Deprecated
     public static boolean invalidateOptionsMenu(Activity activity) {
         activity.invalidateOptionsMenu();
         return true;
@@ -146,8 +224,8 @@
      *                supplied here; there are no supported definitions for
      *                building it manually.
      */
-    public static void startActivityForResult(Activity activity, Intent intent, int requestCode,
-            @Nullable Bundle options) {
+    public static void startActivityForResult(@NonNull Activity activity, @NonNull Intent intent,
+            int requestCode, @Nullable Bundle options) {
         if (Build.VERSION.SDK_INT >= 16) {
             activity.startActivityForResult(intent, requestCode, options);
         } else {
@@ -181,9 +259,10 @@
      *                supplied here; there are no supported definitions for
      *                building it manually.
      */
-    public static void startIntentSenderForResult(Activity activity, IntentSender intent,
-            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
-            int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException {
+    public static void startIntentSenderForResult(@NonNull Activity activity,
+            @NonNull IntentSender intent, int requestCode, @Nullable Intent fillInIntent,
+            int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options)
+            throws IntentSender.SendIntentException {
         if (Build.VERSION.SDK_INT >= 16) {
             activity.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
                     flagsValues, extraFlags, options);
@@ -200,7 +279,7 @@
      * <p>On Android 4.1+ calling this method will call through to the native version of this
      * method. For other platforms {@link Activity#finish()} will be called instead.</p>
      */
-    public static void finishAffinity(Activity activity) {
+    public static void finishAffinity(@NonNull Activity activity) {
         if (Build.VERSION.SDK_INT >= 16) {
             activity.finishAffinity();
         } else {
@@ -217,7 +296,7 @@
      * <p>On Android 4.4 or lower, this method only finishes the Activity with no
      * special exit transition.</p>
      */
-    public static void finishAfterTransition(Activity activity) {
+    public static void finishAfterTransition(@NonNull Activity activity) {
         if (Build.VERSION.SDK_INT >= 21) {
             activity.finishAfterTransition();
         } else {
@@ -242,7 +321,7 @@
      * referrer information, applications can spoof it.</p>
      */
     @Nullable
-    public static Uri getReferrer(Activity activity) {
+    public static Uri getReferrer(@NonNull Activity activity) {
         if (Build.VERSION.SDK_INT >= 22) {
             return activity.getReferrer();
         }
@@ -266,8 +345,8 @@
      *
      * @param callback Used to manipulate shared element transitions on the launched Activity.
      */
-    public static void setEnterSharedElementCallback(Activity activity,
-            SharedElementCallback callback) {
+    public static void setEnterSharedElementCallback(@NonNull Activity activity,
+            @Nullable SharedElementCallback callback) {
         if (Build.VERSION.SDK_INT >= 23) {
             android.app.SharedElementCallback frameworkCallback = callback != null
                     ? new SharedElementCallback23Impl(callback)
@@ -290,8 +369,8 @@
      *
      * @param callback Used to manipulate shared element transitions on the launching Activity.
      */
-    public static void setExitSharedElementCallback(Activity activity,
-            SharedElementCallback callback) {
+    public static void setExitSharedElementCallback(@NonNull Activity activity,
+            @Nullable SharedElementCallback callback) {
         if (Build.VERSION.SDK_INT >= 23) {
             android.app.SharedElementCallback frameworkCallback = callback != null
                     ? new SharedElementCallback23Impl(callback)
@@ -305,13 +384,13 @@
         }
     }
 
-    public static void postponeEnterTransition(Activity activity) {
+    public static void postponeEnterTransition(@NonNull Activity activity) {
         if (Build.VERSION.SDK_INT >= 21) {
             activity.postponeEnterTransition();
         }
     }
 
-    public static void startPostponedEnterTransition(Activity activity) {
+    public static void startPostponedEnterTransition(@NonNull Activity activity) {
         if (Build.VERSION.SDK_INT >= 21) {
             activity.startPostponedEnterTransition();
         }
@@ -386,6 +465,12 @@
      */
     public static void requestPermissions(final @NonNull Activity activity,
             final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
+        if (sDelegate != null
+                && sDelegate.requestPermissions(activity, permissions, requestCode)) {
+            // Delegate has handled the permission request.
+            return;
+        }
+
         if (Build.VERSION.SDK_INT >= 23) {
             if (activity instanceof RequestPermissionsRequestCodeValidator) {
                 ((RequestPermissionsRequestCodeValidator) activity)
diff --git a/android/support/v4/app/ActivityOptionsCompat.java b/android/support/v4/app/ActivityOptionsCompat.java
index 7b5916f..6676805 100644
--- a/android/support/v4/app/ActivityOptionsCompat.java
+++ b/android/support/v4/app/ActivityOptionsCompat.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.v4.util.Pair;
@@ -60,7 +61,8 @@
      * @return Returns a new ActivityOptions object that you can use to supply
      * these options as the options Bundle when starting an activity.
      */
-    public static ActivityOptionsCompat makeCustomAnimation(Context context,
+    @NonNull
+    public static ActivityOptionsCompat makeCustomAnimation(@NonNull Context context,
             int enterResId, int exitResId) {
         if (Build.VERSION.SDK_INT >= 16) {
             return createImpl(ActivityOptions.makeCustomAnimation(context, enterResId, exitResId));
@@ -88,7 +90,8 @@
      * @return Returns a new ActivityOptions object that you can use to supply
      * these options as the options Bundle when starting an activity.
      */
-    public static ActivityOptionsCompat makeScaleUpAnimation(View source,
+    @NonNull
+    public static ActivityOptionsCompat makeScaleUpAnimation(@NonNull View source,
             int startX, int startY, int startWidth, int startHeight) {
         if (Build.VERSION.SDK_INT >= 16) {
             return createImpl(ActivityOptions.makeScaleUpAnimation(
@@ -111,7 +114,8 @@
      * @return Returns a new ActivityOptions object that you can use to
      * supply these options as the options Bundle when starting an activity.
      */
-    public static ActivityOptionsCompat makeClipRevealAnimation(View source,
+    @NonNull
+    public static ActivityOptionsCompat makeClipRevealAnimation(@NonNull View source,
             int startX, int startY, int width, int height) {
         if (Build.VERSION.SDK_INT >= 23) {
             return createImpl(ActivityOptions.makeClipRevealAnimation(
@@ -139,8 +143,9 @@
      * @return Returns a new ActivityOptions object that you can use to supply
      * these options as the options Bundle when starting an activity.
      */
-    public static ActivityOptionsCompat makeThumbnailScaleUpAnimation(View source,
-            Bitmap thumbnail, int startX, int startY) {
+    @NonNull
+    public static ActivityOptionsCompat makeThumbnailScaleUpAnimation(@NonNull View source,
+            @NonNull Bitmap thumbnail, int startX, int startY) {
         if (Build.VERSION.SDK_INT >= 16) {
             return createImpl(ActivityOptions.makeThumbnailScaleUpAnimation(
                     source, thumbnail, startX, startY));
@@ -166,8 +171,9 @@
      * @return Returns a new ActivityOptions object that you can use to
      *         supply these options as the options Bundle when starting an activity.
      */
-    public static ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity,
-            View sharedElement, String sharedElementName) {
+    @NonNull
+    public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,
+            @NonNull View sharedElement, @NonNull String sharedElementName) {
         if (Build.VERSION.SDK_INT >= 21) {
             return createImpl(ActivityOptions.makeSceneTransitionAnimation(
                     activity, sharedElement, sharedElementName));
@@ -192,8 +198,9 @@
      * @return Returns a new ActivityOptions object that you can use to
      *         supply these options as the options Bundle when starting an activity.
      */
+    @NonNull
     @SuppressWarnings("unchecked")
-    public static ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity,
+    public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,
             Pair<View, String>... sharedElements) {
         if (Build.VERSION.SDK_INT >= 21) {
             android.util.Pair<View, String>[] pairs = null;
@@ -219,6 +226,7 @@
      * {@link android.R.attr#launchMode launchMode} values of
      * <code>singleInstance</code> or <code>singleTask</code>.
      */
+    @NonNull
     public static ActivityOptionsCompat makeTaskLaunchBehind() {
         if (Build.VERSION.SDK_INT >= 21) {
             return createImpl(ActivityOptions.makeTaskLaunchBehind());
@@ -230,6 +238,7 @@
      * Create a basic ActivityOptions that has no special animation associated with it.
      * Other options can still be set.
      */
+    @NonNull
     public static ActivityOptionsCompat makeBasic() {
         if (Build.VERSION.SDK_INT >= 23) {
             return createImpl(ActivityOptions.makeBasic());
@@ -314,8 +323,9 @@
      * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
      * @param screenSpacePixelRect Launch bounds to use for the activity or null for fullscreen.
      */
+    @NonNull
     public ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
-        return null;
+        return this;
     }
 
     /**
@@ -335,6 +345,7 @@
      * object; you must not modify it, but can supply it to the startActivity
      * methods that take an options Bundle.
      */
+    @Nullable
     public Bundle toBundle() {
         return null;
     }
@@ -344,7 +355,7 @@
      * otherOptions. Any values defined in otherOptions replace those in the
      * base options.
      */
-    public void update(ActivityOptionsCompat otherOptions) {
+    public void update(@NonNull ActivityOptionsCompat otherOptions) {
         // Do nothing.
     }
 
@@ -372,7 +383,7 @@
      *
      * @param receiver A broadcast receiver that will receive the report.
      */
-    public void requestUsageTimeReport(PendingIntent receiver) {
+    public void requestUsageTimeReport(@NonNull PendingIntent receiver) {
         // Do nothing.
     }
 }
diff --git a/android/support/v4/app/AlarmManagerCompat.java b/android/support/v4/app/AlarmManagerCompat.java
index 5a4582b..a297cb5 100644
--- a/android/support/v4/app/AlarmManagerCompat.java
+++ b/android/support/v4/app/AlarmManagerCompat.java
@@ -19,6 +19,7 @@
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.os.Build;
+import android.support.annotation.NonNull;
 
 /**
  * Compatibility library for {@link AlarmManager} with fallbacks for older platforms.
@@ -52,8 +53,8 @@
      * @see android.content.Context#registerReceiver
      * @see android.content.Intent#filterEquals
      */
-    public static void setAlarmClock(AlarmManager alarmManager, long triggerTime,
-            PendingIntent showIntent, PendingIntent operation) {
+    public static void setAlarmClock(@NonNull AlarmManager alarmManager, long triggerTime,
+            @NonNull PendingIntent showIntent, @NonNull PendingIntent operation) {
         if (Build.VERSION.SDK_INT >= 21) {
             alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(triggerTime, showIntent),
                     operation);
@@ -110,8 +111,8 @@
      * @see AlarmManager#RTC
      * @see AlarmManager#RTC_WAKEUP
      */
-    public static void setAndAllowWhileIdle(AlarmManager alarmManager, int type,
-            long triggerAtMillis, PendingIntent operation) {
+    public static void setAndAllowWhileIdle(@NonNull AlarmManager alarmManager, int type,
+            long triggerAtMillis, @NonNull PendingIntent operation) {
         if (Build.VERSION.SDK_INT >= 23) {
             alarmManager.setAndAllowWhileIdle(type, triggerAtMillis, operation);
         } else {
@@ -155,8 +156,8 @@
      * @see AlarmManager#RTC
      * @see AlarmManager#RTC_WAKEUP
      */
-    public static void setExact(AlarmManager alarmManager, int type, long triggerAtMillis,
-            PendingIntent operation) {
+    public static void setExact(@NonNull AlarmManager alarmManager, int type, long triggerAtMillis,
+            @NonNull PendingIntent operation) {
         if (Build.VERSION.SDK_INT >= 19) {
             alarmManager.setExact(type, triggerAtMillis, operation);
         } else {
@@ -215,8 +216,8 @@
      * @see AlarmManager#RTC
      * @see AlarmManager#RTC_WAKEUP
      */
-    public static void setExactAndAllowWhileIdle(AlarmManager alarmManager, int type,
-            long triggerAtMillis, PendingIntent operation) {
+    public static void setExactAndAllowWhileIdle(@NonNull AlarmManager alarmManager, int type,
+            long triggerAtMillis, @NonNull PendingIntent operation) {
         if (Build.VERSION.SDK_INT >= 23) {
             alarmManager.setExactAndAllowWhileIdle(type, triggerAtMillis, operation);
         } else {
diff --git a/android/support/v4/app/AppLaunchChecker.java b/android/support/v4/app/AppLaunchChecker.java
index f8beb91..af9512a 100644
--- a/android/support/v4/app/AppLaunchChecker.java
+++ b/android/support/v4/app/AppLaunchChecker.java
@@ -22,8 +22,8 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.v4.content.IntentCompat;
-import android.support.v4.content.SharedPreferencesCompat;
 
 /**
  * This class provides APIs for determining how an app has been launched.
@@ -46,7 +46,7 @@
      * @param context Context to check
      * @return true if this app has been started by the user from the launcher at least once
      */
-    public static boolean hasStartedFromLauncher(Context context) {
+    public static boolean hasStartedFromLauncher(@NonNull Context context) {
         return context.getSharedPreferences(SHARED_PREFS_NAME, 0)
                 .getBoolean(KEY_STARTED_FROM_LAUNCHER, false);
     }
@@ -62,7 +62,7 @@
      *
      * @param activity the Activity currently running onCreate
      */
-    public static void onActivityCreate(Activity activity) {
+    public static void onActivityCreate(@NonNull Activity activity) {
         final SharedPreferences sp = activity.getSharedPreferences(SHARED_PREFS_NAME, 0);
         if (sp.getBoolean(KEY_STARTED_FROM_LAUNCHER, false)) {
             return;
@@ -76,8 +76,7 @@
         if (Intent.ACTION_MAIN.equals(launchIntent.getAction())
                 && (launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
                 || launchIntent.hasCategory(IntentCompat.CATEGORY_LEANBACK_LAUNCHER))) {
-            SharedPreferencesCompat.EditorCompat.getInstance().apply(
-                    sp.edit().putBoolean(KEY_STARTED_FROM_LAUNCHER, true));
+            sp.edit().putBoolean(KEY_STARTED_FROM_LAUNCHER, true).apply();
         }
     }
 }
diff --git a/android/support/v4/app/AppOpsManagerCompat.java b/android/support/v4/app/AppOpsManagerCompat.java
index ce2d2c6..7e97199 100644
--- a/android/support/v4/app/AppOpsManagerCompat.java
+++ b/android/support/v4/app/AppOpsManagerCompat.java
@@ -21,6 +21,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 /**
  * Helper for accessing features in {@link android.app.AppOpsManager}.
@@ -56,6 +57,7 @@
      * @param permission The permission.
      * @return The app op associated with the permission or null.
      */
+    @Nullable
     public static String permissionToOp(@NonNull String permission) {
         if (SDK_INT >= 23) {
             return AppOpsManager.permissionToOp(permission);
diff --git a/android/support/v4/app/BundleCompat.java b/android/support/v4/app/BundleCompat.java
index e5fc302..21d730d 100644
--- a/android/support/v4/app/BundleCompat.java
+++ b/android/support/v4/app/BundleCompat.java
@@ -19,6 +19,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 
 import java.lang.reflect.InvocationTargetException;
@@ -94,7 +96,8 @@
      * @param key    The key to use while getting the {@link IBinder}.
      * @return       The {@link IBinder} that was obtained.
      */
-    public static IBinder getBinder(Bundle bundle, String key) {
+    @Nullable
+    public static IBinder getBinder(@NonNull Bundle bundle, @Nullable String key) {
         if (Build.VERSION.SDK_INT >= 18) {
             return bundle.getBinder(key);
         } else {
@@ -109,7 +112,8 @@
      * @param key    The key to use while putting the {@link IBinder}.
      * @param binder The {@link IBinder} to put.
      */
-    public static void putBinder(Bundle bundle, String key, IBinder binder) {
+    public static void putBinder(@NonNull Bundle bundle, @Nullable String key,
+            @Nullable IBinder binder) {
         if (Build.VERSION.SDK_INT >= 18) {
             bundle.putBinder(key, binder);
         } else {
diff --git a/android/support/v4/app/Fragment.java b/android/support/v4/app/Fragment.java
index ba74521..e734a27 100644
--- a/android/support/v4/app/Fragment.java
+++ b/android/support/v4/app/Fragment.java
@@ -463,6 +463,7 @@
     /**
      * Get the tag name of the fragment, if specified.
      */
+    @Nullable
     final public String getTag() {
         return mTag;
     }
@@ -474,7 +475,7 @@
      * <p>This method cannot be called if the fragment is added to a FragmentManager and
      * if {@link #isStateSaved()} would return true.</p>
      */
-    public void setArguments(Bundle args) {
+    public void setArguments(@Nullable Bundle args) {
         if (mIndex >= 0 && isStateSaved()) {
             throw new IllegalStateException("Fragment already active and state has been saved");
         }
@@ -485,6 +486,7 @@
      * Return the arguments supplied when the fragment was instantiated,
      * if any.
      */
+    @Nullable
     final public Bundle getArguments() {
         return mArguments;
     }
@@ -512,7 +514,7 @@
      *
      * @param state The state the fragment should be restored from.
      */
-    public void setInitialSavedState(SavedState state) {
+    public void setInitialSavedState(@Nullable SavedState state) {
         if (mIndex >= 0) {
             throw new IllegalStateException("Fragment already active");
         }
@@ -532,7 +534,7 @@
      * are going to call back with {@link #onActivityResult(int, int, Intent)}.
      */
     @SuppressWarnings("ReferenceEquality")
-    public void setTargetFragment(Fragment fragment, int requestCode) {
+    public void setTargetFragment(@Nullable Fragment fragment, int requestCode) {
         // Don't allow a caller to set a target fragment in another FragmentManager,
         // but there's a snag: people do set target fragments before fragments get added.
         // We'll have the FragmentManager check that for validity when we move
@@ -558,6 +560,7 @@
     /**
      * Return the target fragment set by {@link #setTargetFragment}.
      */
+    @Nullable
     final public Fragment getTargetFragment() {
         return mTarget;
     }
@@ -572,6 +575,7 @@
     /**
      * Return the {@link Context} this fragment is currently associated with.
      */
+    @Nullable
     public Context getContext() {
         return mHost == null ? null : mHost.getContext();
     }
@@ -581,6 +585,7 @@
      * May return {@code null} if the fragment is associated with a {@link Context}
      * instead.
      */
+    @Nullable
     final public FragmentActivity getActivity() {
         return mHost == null ? null : (FragmentActivity) mHost.getActivity();
     }
@@ -589,6 +594,7 @@
      * Return the host object of this fragment. May return {@code null} if the fragment
      * isn't currently being hosted.
      */
+    @Nullable
     final public Object getHost() {
         return mHost == null ? null : mHost.onGetHost();
     }
@@ -596,6 +602,7 @@
     /**
      * Return <code>getActivity().getResources()</code>.
      */
+    @NonNull
     final public Resources getResources() {
         if (mHost == null) {
             throw new IllegalStateException("Fragment " + this + " not attached to Activity");
@@ -609,6 +616,7 @@
      *
      * @param resId Resource id for the CharSequence text
      */
+    @NonNull
     public final CharSequence getText(@StringRes int resId) {
         return getResources().getText(resId);
     }
@@ -619,6 +627,7 @@
      *
      * @param resId Resource id for the string
      */
+    @NonNull
     public final String getString(@StringRes int resId) {
         return getResources().getString(resId);
     }
@@ -631,7 +640,7 @@
      * @param resId Resource id for the format string
      * @param formatArgs The format arguments that will be used for substitution.
      */
-
+    @NonNull
     public final String getString(@StringRes int resId, Object... formatArgs) {
         return getResources().getString(resId, formatArgs);
     }
@@ -646,6 +655,7 @@
      * <p>If this Fragment is a child of another Fragment, the FragmentManager
      * returned here will be the parent's {@link #getChildFragmentManager()}.
      */
+    @Nullable
     final public FragmentManager getFragmentManager() {
         return mFragmentManager;
     }
@@ -654,6 +664,7 @@
      * Return a private FragmentManager for placing and managing Fragments
      * inside of this Fragment.
      */
+    @NonNull
     final public FragmentManager getChildFragmentManager() {
         if (mChildFragmentManager == null) {
             instantiateChildFragmentManager();
@@ -674,6 +685,7 @@
      * Return this fragment's child FragmentManager one has been previously created,
      * otherwise null.
      */
+    @Nullable
     FragmentManager peekChildFragmentManager() {
         return mChildFragmentManager;
     }
@@ -682,6 +694,7 @@
      * Returns the parent Fragment containing this Fragment.  If this Fragment
      * is attached directly to an Activity, returns null.
      */
+    @Nullable
     final public Fragment getParentFragment() {
         return mParentFragment;
     }
@@ -1082,7 +1095,8 @@
      * a previous saved state, this is the state.
      * @return The LayoutInflater used to inflate Views of this Fragment.
      */
-    public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
+    @NonNull
+    public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
         // TODO: move the implementation in getLayoutInflater to here
         return getLayoutInflater(savedInstanceState);
     }
@@ -1113,7 +1127,8 @@
      * a previous saved state, this is the state.
      * @return The LayoutInflater used to inflate Views of this Fragment.
      */
-    LayoutInflater performGetLayoutInflater(Bundle savedInstanceState) {
+    @NonNull
+    LayoutInflater performGetLayoutInflater(@Nullable Bundle savedInstanceState) {
         LayoutInflater layoutInflater = onGetLayoutInflater(savedInstanceState);
         mLayoutInflater = layoutInflater;
         return mLayoutInflater;
@@ -1129,8 +1144,9 @@
      * {@link #getLayoutInflater()} instead of this method.
      */
     @Deprecated
+    @NonNull
     @RestrictTo(LIBRARY_GROUP)
-    public LayoutInflater getLayoutInflater(Bundle savedFragmentState) {
+    public LayoutInflater getLayoutInflater(@Nullable Bundle savedFragmentState) {
         if (mHost == null) {
             throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the "
                     + "Fragment is attached to the FragmentManager.");
@@ -1157,24 +1173,24 @@
      * <p>Here is a typical implementation of a fragment that can take parameters
      * both through attributes supplied here as well from {@link #getArguments()}:</p>
      *
-     * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentArgumentsSupport.java
+     * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentArgumentsSupport.java
      *      fragment}
      *
      * <p>Note that parsing the XML attributes uses a "styleable" resource.  The
      * declaration for the styleable used here is:</p>
      *
-     * {@sample frameworks/support/samples/Support4Demos/res/values/attrs.xml fragment_arguments}
+     * {@sample frameworks/support/samples/Support4Demos/src/main/res/values/attrs.xml fragment_arguments}
      *
      * <p>The fragment can then be declared within its activity's content layout
      * through a tag like this:</p>
      *
-     * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_arguments_support.xml from_attributes}
+     * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_arguments_support.xml from_attributes}
      *
      * <p>This fragment can also be created dynamically from arguments given
      * at runtime in the arguments Bundle; here is an example of doing so at
      * creation of the containing activity:</p>
      *
-     * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentArgumentsSupport.java
+     * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentArgumentsSupport.java
      *      create}
      *
      * @param context The Activity that is inflating this fragment.
@@ -1356,7 +1372,7 @@
      * @return Return the View for the fragment's UI, or null.
      */
     @Nullable
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             @Nullable Bundle savedInstanceState) {
         return null;
     }
@@ -1371,7 +1387,7 @@
      * @param savedInstanceState If non-null, this fragment is being re-constructed
      * from a previous saved state as given here.
      */
-    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
     }
 
     /**
@@ -1469,7 +1485,7 @@
      *
      * @param outState Bundle in which to place your saved state.
      */
-    public void onSaveInstanceState(Bundle outState) {
+    public void onSaveInstanceState(@NonNull Bundle outState) {
     }
 
     /**
@@ -1768,7 +1784,7 @@
      *
      * @param transition The Transition to use to move Views into the initial Scene.
      */
-    public void setEnterTransition(Object transition) {
+    public void setEnterTransition(@Nullable Object transition) {
         ensureAnimationInfo().mEnterTransition = transition;
     }
 
@@ -1781,6 +1797,7 @@
      *
      * @return the Transition to use to move Views into the initial Scene.
      */
+    @Nullable
     public Object getEnterTransition() {
         if (mAnimationInfo == null) {
             return null;
@@ -1802,7 +1819,7 @@
      *                   is preparing to close. <code>transition</code> must be an
      *                   android.transition.Transition.
      */
-    public void setReturnTransition(Object transition) {
+    public void setReturnTransition(@Nullable Object transition) {
         ensureAnimationInfo().mReturnTransition = transition;
     }
 
@@ -1818,6 +1835,7 @@
      * @return the Transition to use to move Views out of the Scene when the Fragment
      *         is preparing to close.
      */
+    @Nullable
     public Object getReturnTransition() {
         if (mAnimationInfo == null) {
             return null;
@@ -1839,7 +1857,7 @@
      *                   is being closed not due to popping the back stack. <code>transition</code>
      *                   must be an android.transition.Transition.
      */
-    public void setExitTransition(Object transition) {
+    public void setExitTransition(@Nullable Object transition) {
         ensureAnimationInfo().mExitTransition = transition;
     }
 
@@ -1855,6 +1873,7 @@
      * @return the Transition to use to move Views out of the Scene when the Fragment
      *         is being closed not due to popping the back stack.
      */
+    @Nullable
     public Object getExitTransition() {
         if (mAnimationInfo == null) {
             return null;
@@ -1875,7 +1894,7 @@
      *                   previously-started Activity. <code>transition</code>
      *                   must be an android.transition.Transition.
      */
-    public void setReenterTransition(Object transition) {
+    public void setReenterTransition(@Nullable Object transition) {
         ensureAnimationInfo().mReenterTransition = transition;
     }
 
@@ -1908,7 +1927,7 @@
      * @param transition The Transition to use for shared elements transferred into the content
      *                   Scene.  <code>transition</code> must be an android.transition.Transition.
      */
-    public void setSharedElementEnterTransition(Object transition) {
+    public void setSharedElementEnterTransition(@Nullable Object transition) {
         ensureAnimationInfo().mSharedElementEnterTransition = transition;
     }
 
@@ -1921,6 +1940,7 @@
      * @return The Transition to use for shared elements transferred into the content
      *                   Scene.
      */
+    @Nullable
     public Object getSharedElementEnterTransition() {
         if (mAnimationInfo == null) {
             return null;
@@ -1940,7 +1960,7 @@
      * @param transition The Transition to use for shared elements transferred out of the content
      *                   Scene. <code>transition</code> must be an android.transition.Transition.
      */
-    public void setSharedElementReturnTransition(Object transition) {
+    public void setSharedElementReturnTransition(@Nullable Object transition) {
         ensureAnimationInfo().mSharedElementReturnTransition = transition;
     }
 
@@ -1956,6 +1976,7 @@
      * @return The Transition to use for shared elements transferred out of the content
      *                   Scene.
      */
+    @Nullable
     public Object getSharedElementReturnTransition() {
         if (mAnimationInfo == null) {
             return null;
@@ -2231,8 +2252,8 @@
         mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
     }
 
-    View performCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+    View performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
         if (mChildFragmentManager != null) {
             mChildFragmentManager.noteStateNotSaved();
         }
diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java
index 481f50d..cb3c59a 100644
--- a/android/support/v4/app/FragmentActivity.java
+++ b/android/support/v4/app/FragmentActivity.java
@@ -153,6 +153,13 @@
             return;
         }
 
+        ActivityCompat.PermissionCompatDelegate delegate =
+                ActivityCompat.getPermissionCompatDelegate();
+        if (delegate != null && delegate.onActivityResult(this, requestCode, resultCode, data)) {
+            // Delegate has handled the activity result
+            return;
+        }
+
         super.onActivityResult(requestCode, resultCode, data);
     }
 
@@ -270,6 +277,16 @@
     }
 
     /**
+     * Returns the Lifecycle of the provider.
+     *
+     * @return The lifecycle of the provider.
+     */
+    @Override
+    public Lifecycle getLifecycle() {
+        return super.getLifecycle();
+    }
+
+    /**
      * Perform initialization of all fragments and loaders.
      */
     @SuppressWarnings("deprecation")
@@ -750,6 +767,7 @@
     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
             @NonNull int[] grantResults) {
+        mFragments.noteStateNotSaved();
         int index = (requestCode >> 16) & 0xffff;
         if (index != 0) {
             index--;
diff --git a/android/support/v4/app/FragmentHostCallback.java b/android/support/v4/app/FragmentHostCallback.java
index 7dc9f59..eeae62a 100644
--- a/android/support/v4/app/FragmentHostCallback.java
+++ b/android/support/v4/app/FragmentHostCallback.java
@@ -94,8 +94,9 @@
      * Return a {@link LayoutInflater}.
      * See {@link Activity#getLayoutInflater()}.
      */
+    @NonNull
     public LayoutInflater onGetLayoutInflater() {
-        return (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        return LayoutInflater.from(mContext);
     }
 
     /**
diff --git a/android/support/v4/app/FragmentManager.java b/android/support/v4/app/FragmentManager.java
index 6e6caa0..16103f8 100644
--- a/android/support/v4/app/FragmentManager.java
+++ b/android/support/v4/app/FragmentManager.java
@@ -1605,12 +1605,21 @@
                 @Override
                 public void onAnimationEnd(Animation animation) {
                     super.onAnimationEnd(animation);
-                    container.endViewTransition(viewToAnimate);
+                    // onAnimationEnd() comes during draw(), so there can still be some
+                    // draw events happening after this call. We don't want to detach
+                    // the view until after the onAnimationEnd()
+                    container.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            container.endViewTransition(viewToAnimate);
 
-                    if (fragment.getAnimatingAway() != null) {
-                        fragment.setAnimatingAway(null);
-                        moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
-                    }
+                            if (fragment.getAnimatingAway() != null) {
+                                fragment.setAnimatingAway(null);
+                                moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0,
+                                        false);
+                            }
+                        }
+                    });
                 }
             });
             setHWLayerAnimListenerIfAlpha(viewToAnimate, anim);
diff --git a/android/support/v4/app/FragmentPagerAdapter.java b/android/support/v4/app/FragmentPagerAdapter.java
index 61b181d..6b25d2f 100644
--- a/android/support/v4/app/FragmentPagerAdapter.java
+++ b/android/support/v4/app/FragmentPagerAdapter.java
@@ -44,18 +44,18 @@
  * <p>Here is an example implementation of a pager containing fragments of
  * lists:
  *
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentPagerSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentPagerSupport.java
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
  *
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager_list</code> resource containing each
  * individual fragment's layout is:
  *
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
  *      complete}
  */
 public abstract class FragmentPagerAdapter extends PagerAdapter {
diff --git a/android/support/v4/app/FragmentStatePagerAdapter.java b/android/support/v4/app/FragmentStatePagerAdapter.java
index fc27c4f..040f2db 100644
--- a/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -47,18 +47,18 @@
  * <p>Here is an example implementation of a pager containing fragments of
  * lists:
  *
- * {@sample frameworks/support/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java
+ * {@sample frameworks/support/samples/Support13Demos/src/main/java/com/example/android/supportv13/app/FragmentStatePagerSupport.java
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
  *
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager.xml
  *      complete}
  *
  * <p>The <code>R.layout.fragment_pager_list</code> resource containing each
  * individual fragment's layout is:
  *
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager_list.xml
  *      complete}
  */
 public abstract class FragmentStatePagerAdapter extends PagerAdapter {
diff --git a/android/support/v4/app/FragmentTabHost.java b/android/support/v4/app/FragmentTabHost.java
index 09b89b7..6b914fe 100644
--- a/android/support/v4/app/FragmentTabHost.java
+++ b/android/support/v4/app/FragmentTabHost.java
@@ -41,12 +41,12 @@
  *
  * <p>Here is a simple example of using a FragmentTabHost in an Activity:
  *
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
  *      complete}
  *
  * <p>This can also be used inside of a fragment through fragment nesting:
  *
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
  *      complete}
  */
 public class FragmentTabHost extends TabHost
diff --git a/android/support/v4/app/JobIntentService.java b/android/support/v4/app/JobIntentService.java
index c0d7f13..87b7441 100644
--- a/android/support/v4/app/JobIntentService.java
+++ b/android/support/v4/app/JobIntentService.java
@@ -84,7 +84,7 @@
  *
  * <p>Here is an example implementation of this class:</p>
  *
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/SimpleJobIntentService.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/SimpleJobIntentService.java
  *      complete}
  */
 public abstract class JobIntentService extends Service {
diff --git a/android/support/v4/app/ListFragment.java b/android/support/v4/app/ListFragment.java
index 21617ad..496bd8e 100644
--- a/android/support/v4/app/ListFragment.java
+++ b/android/support/v4/app/ListFragment.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -142,7 +144,7 @@
      * Attach to list view once the view hierarchy has been created.
      */
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         ensureList();
     }
diff --git a/android/support/v4/app/NavUtils.java b/android/support/v4/app/NavUtils.java
index 99d4493..d259417 100644
--- a/android/support/v4/app/NavUtils.java
+++ b/android/support/v4/app/NavUtils.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.util.Log;
 
@@ -53,7 +54,8 @@
      * @return true if navigating up should recreate a new task stack, false if the same task
      *         should be used for the destination
      */
-    public static boolean shouldUpRecreateTask(Activity sourceActivity, Intent targetIntent) {
+    public static boolean shouldUpRecreateTask(@NonNull Activity sourceActivity,
+            @NonNull Intent targetIntent) {
         if (Build.VERSION.SDK_INT >= 16) {
             return sourceActivity.shouldUpRecreateTask(targetIntent);
         } else {
@@ -74,7 +76,7 @@
      *
      * @param sourceActivity The current activity from which the user is attempting to navigate up
      */
-    public static void navigateUpFromSameTask(Activity sourceActivity) {
+    public static void navigateUpFromSameTask(@NonNull Activity sourceActivity) {
         Intent upIntent = getParentActivityIntent(sourceActivity);
 
         if (upIntent == null) {
@@ -101,7 +103,7 @@
      * @param sourceActivity The current activity from which the user is attempting to navigate up
      * @param upIntent An intent representing the target destination for up navigation
      */
-    public static void navigateUpTo(Activity sourceActivity, Intent upIntent) {
+    public static void navigateUpTo(@NonNull Activity sourceActivity, @NonNull Intent upIntent) {
         if (Build.VERSION.SDK_INT >= 16) {
             sourceActivity.navigateUpTo(upIntent);
         } else {
@@ -121,7 +123,8 @@
      * @param sourceActivity Activity to fetch a parent intent for
      * @return a new Intent targeting the defined parent activity of sourceActivity
      */
-    public static Intent getParentActivityIntent(Activity sourceActivity) {
+    @Nullable
+    public static Intent getParentActivityIntent(@NonNull Activity sourceActivity) {
         if (Build.VERSION.SDK_INT >= 16) {
             // Prefer the "real" JB definition if available,
             // else fall back to the meta-data element.
@@ -157,7 +160,9 @@
      * @return a new Intent targeting the defined parent activity of sourceActivity
      * @throws NameNotFoundException if the ComponentName for sourceActivityClass is invalid
      */
-    public static Intent getParentActivityIntent(Context context, Class<?> sourceActivityClass)
+    @Nullable
+    public static Intent getParentActivityIntent(@NonNull Context context,
+            @NonNull Class<?> sourceActivityClass)
             throws NameNotFoundException {
         String parentActivity = getParentActivityName(context,
                 new ComponentName(context, sourceActivityClass));
@@ -182,7 +187,9 @@
      * @return a new Intent targeting the defined parent activity of sourceActivity
      * @throws NameNotFoundException if the ComponentName for sourceActivityClass is invalid
      */
-    public static Intent getParentActivityIntent(Context context, ComponentName componentName)
+    @Nullable
+    public static Intent getParentActivityIntent(@NonNull Context context,
+            @NonNull ComponentName componentName)
             throws NameNotFoundException {
         String parentActivity = getParentActivityName(context, componentName);
         if (parentActivity == null) return null;
@@ -207,7 +214,7 @@
      *         it was not specified
      */
     @Nullable
-    public static String getParentActivityName(Activity sourceActivity) {
+    public static String getParentActivityName(@NonNull Activity sourceActivity) {
         try {
             return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
         } catch (NameNotFoundException e) {
@@ -226,7 +233,8 @@
      *         it was not specified
      */
     @Nullable
-    public static String getParentActivityName(Context context, ComponentName componentName)
+    public static String getParentActivityName(@NonNull Context context,
+            @NonNull ComponentName componentName)
             throws NameNotFoundException {
         PackageManager pm = context.getPackageManager();
         ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
diff --git a/android/support/v4/app/NotificationManagerCompat.java b/android/support/v4/app/NotificationManagerCompat.java
index 93775ec..1a0f1bc 100644
--- a/android/support/v4/app/NotificationManagerCompat.java
+++ b/android/support/v4/app/NotificationManagerCompat.java
@@ -36,6 +36,8 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 
 import java.lang.reflect.Field;
@@ -144,7 +146,8 @@
     public static final int IMPORTANCE_MAX = 5;
 
     /** Get a {@link NotificationManagerCompat} instance for a provided context. */
-    public static NotificationManagerCompat from(Context context) {
+    @NonNull
+    public static NotificationManagerCompat from(@NonNull Context context) {
         return new NotificationManagerCompat(context);
     }
 
@@ -167,7 +170,7 @@
      * @param tag the string identifier of the notification.
      * @param id the ID of the notification
      */
-    public void cancel(String tag, int id) {
+    public void cancel(@Nullable String tag, int id) {
         mNotificationManager.cancel(tag, id);
         if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
             pushSideChannelQueue(new CancelTask(mContext.getPackageName(), id, tag));
@@ -197,7 +200,7 @@
      * @param id the ID of the notification. The pair (tag, id) must be unique within your app.
      * @param notification the notification to post to the system
     */
-    public void notify(String tag, int id, Notification notification) {
+    public void notify(@Nullable String tag, int id, @NonNull Notification notification) {
         if (useSideChannelForNotification(notification)) {
             pushSideChannelQueue(new NotifyTask(mContext.getPackageName(), id, tag, notification));
             // Cancel this notification in notification manager if it just transitioned to being
@@ -253,7 +256,8 @@
     /**
      * Get the set of packages that have an enabled notification listener component within them.
      */
-    public static Set<String> getEnabledListenerPackages(Context context) {
+    @NonNull
+    public static Set<String> getEnabledListenerPackages(@NonNull Context context) {
         final String enabledNotificationListeners = Settings.Secure.getString(
                 context.getContentResolver(),
                 SETTING_ENABLED_NOTIFICATION_LISTENERS);
diff --git a/android/support/v4/app/ServiceCompat.java b/android/support/v4/app/ServiceCompat.java
index 1676ee8..2e63b23 100644
--- a/android/support/v4/app/ServiceCompat.java
+++ b/android/support/v4/app/ServiceCompat.java
@@ -22,6 +22,7 @@
 import android.app.Service;
 import android.os.Build;
 import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
 
 import java.lang.annotation.Retention;
@@ -92,7 +93,7 @@
      * {@link #STOP_FOREGROUND_DETACH}.
      * @see Service#startForeground(int, Notification)
      */
-    public static void stopForeground(Service service, @StopForegroundFlags int flags) {
+    public static void stopForeground(@NonNull Service service, @StopForegroundFlags int flags) {
         if (Build.VERSION.SDK_INT >= 24) {
             service.stopForeground(flags);
         } else {
diff --git a/android/support/v4/app/TaskStackBuilder.java b/android/support/v4/app/TaskStackBuilder.java
index dc9a2dc..14aadce 100644
--- a/android/support/v4/app/TaskStackBuilder.java
+++ b/android/support/v4/app/TaskStackBuilder.java
@@ -16,7 +16,6 @@
 
 package android.support.v4.app;
 
-import android.support.annotation.RequiresApi;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -25,6 +24,9 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
 import android.support.v4.content.ContextCompat;
 import android.util.Log;
 
@@ -70,6 +72,7 @@
     private static final String TAG = "TaskStackBuilder";
 
     public interface SupportParentable {
+        @Nullable
         Intent getSupportParentActivityIntent();
     }
 
@@ -117,7 +120,8 @@
      * @param context The context that will launch the new task stack or generate a PendingIntent
      * @return A new TaskStackBuilder
      */
-    public static TaskStackBuilder create(Context context) {
+    @NonNull
+    public static TaskStackBuilder create(@NonNull Context context) {
         return new TaskStackBuilder(context);
     }
 
@@ -142,7 +146,8 @@
      * @param nextIntent Intent for the next Activity in the synthesized task stack
      * @return This TaskStackBuilder for method chaining
      */
-    public TaskStackBuilder addNextIntent(Intent nextIntent) {
+    @NonNull
+    public TaskStackBuilder addNextIntent(@NonNull Intent nextIntent) {
         mIntents.add(nextIntent);
         return this;
     }
@@ -159,7 +164,8 @@
      *                   Its chain of parents as specified in the manifest will be added.
      * @return This TaskStackBuilder for method chaining.
      */
-    public TaskStackBuilder addNextIntentWithParentStack(Intent nextIntent) {
+    @NonNull
+    public TaskStackBuilder addNextIntentWithParentStack(@NonNull Intent nextIntent) {
         ComponentName target = nextIntent.getComponent();
         if (target == null) {
             target = nextIntent.resolveActivity(mSourceContext.getPackageManager());
@@ -178,7 +184,8 @@
      * @param sourceActivity All parents of this activity will be added
      * @return This TaskStackBuilder for method chaining
      */
-    public TaskStackBuilder addParentStack(Activity sourceActivity) {
+    @NonNull
+    public TaskStackBuilder addParentStack(@NonNull Activity sourceActivity) {
         Intent parent = null;
         if (sourceActivity instanceof SupportParentable) {
             parent = ((SupportParentable) sourceActivity).getSupportParentActivityIntent();
@@ -207,7 +214,8 @@
      * @param sourceActivityClass All parents of this activity will be added
      * @return This TaskStackBuilder for method chaining
      */
-    public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
+    @NonNull
+    public TaskStackBuilder addParentStack(@NonNull Class<?> sourceActivityClass) {
         return addParentStack(new ComponentName(mSourceContext, sourceActivityClass));
     }
 
@@ -264,6 +272,7 @@
      * @param index Index from 0-getIntentCount()
      * @return the intent at position index
      */
+    @Nullable
     public Intent editIntentAt(int index) {
         return mIntents.get(index);
     }
@@ -300,7 +309,7 @@
      * @param options Additional options for how the Activity should be started.
      * See {@link android.content.Context#startActivity(Intent, Bundle)}
      */
-    public void startActivities(Bundle options) {
+    public void startActivities(@Nullable Bundle options) {
         if (mIntents.isEmpty()) {
             throw new IllegalStateException(
                     "No intents added to TaskStackBuilder; cannot startActivities");
@@ -325,8 +334,10 @@
      *              {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
      *              {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
      *              intent that can be supplied when the actual send happens.
-     * @return The obtained PendingIntent
+     * @return The obtained PendingIntent.  May return null only if
+     * {@link PendingIntent#FLAG_NO_CREATE} has been supplied.
      */
+    @Nullable
     public PendingIntent getPendingIntent(int requestCode, int flags) {
         return getPendingIntent(requestCode, flags, null);
     }
@@ -342,9 +353,11 @@
      *              intent that can be supplied when the actual send happens.
      * @param options Additional options for how the Activity should be started.
      * See {@link android.content.Context#startActivity(Intent, Bundle)}
-     * @return The obtained PendingIntent
+     * @return The obtained PendingIntent.  May return null only if
+     * {@link PendingIntent#FLAG_NO_CREATE} has been supplied.
      */
-    public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
+    @Nullable
+    public PendingIntent getPendingIntent(int requestCode, int flags, @Nullable Bundle options) {
         if (mIntents.isEmpty()) {
             throw new IllegalStateException(
                     "No intents added to TaskStackBuilder; cannot getPendingIntent");
@@ -364,6 +377,7 @@
      *
      * @return An array containing the intents added to this builder.
      */
+    @NonNull
     public Intent[] getIntents() {
         Intent[] intents = new Intent[mIntents.size()];
         if (intents.length == 0) return intents;
diff --git a/android/support/v4/content/AsyncTaskLoader.java b/android/support/v4/content/AsyncTaskLoader.java
index faa13ad..5882f69 100644
--- a/android/support/v4/content/AsyncTaskLoader.java
+++ b/android/support/v4/content/AsyncTaskLoader.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.os.OperationCanceledException;
 import android.support.v4.util.TimeUtils;
@@ -121,11 +123,11 @@
     long mLastLoadCompleteTime = -10000;
     Handler mHandler;
 
-    public AsyncTaskLoader(Context context) {
+    public AsyncTaskLoader(@NonNull Context context) {
         this(context, ModernAsyncTask.THREAD_POOL_EXECUTOR);
     }
 
-    private AsyncTaskLoader(Context context, Executor executor) {
+    private AsyncTaskLoader(@NonNull Context context, @NonNull Executor executor) {
         super(context);
         mExecutor = executor;
     }
@@ -200,7 +202,7 @@
      * @param data The value that was returned by {@link #loadInBackground}, or null
      * if the task threw {@link OperationCanceledException}.
      */
-    public void onCanceled(D data) {
+    public void onCanceled(@Nullable D data) {
     }
 
     void executePendingTask() {
@@ -284,6 +286,7 @@
      * @see #cancelLoadInBackground
      * @see #onCanceled
      */
+    @Nullable
     public abstract D loadInBackground();
 
     /**
@@ -298,6 +301,7 @@
      *
      * @see #loadInBackground
      */
+    @Nullable
     protected D onLoadInBackground() {
         return loadInBackground();
     }
diff --git a/android/support/v4/content/ContextCompat.java b/android/support/v4/content/ContextCompat.java
index fdbe32f..8a4fd7a 100644
--- a/android/support/v4/content/ContextCompat.java
+++ b/android/support/v4/content/ContextCompat.java
@@ -79,7 +79,7 @@
      *                length-1 will correspond to the top activity on the resulting task stack.
      * @return true if the underlying API was available and the call was successful, false otherwise
      */
-    public static boolean startActivities(Context context, Intent[] intents) {
+    public static boolean startActivities(@NonNull Context context, @NonNull Intent[] intents) {
         return startActivities(context, intents, null);
     }
 
@@ -110,7 +110,8 @@
      * See {@link android.content.Context#startActivity(Intent, android.os.Bundle)}
      * @return true if the underlying API was available and the call was successful, false otherwise
      */
-    public static boolean startActivities(Context context, Intent[] intents, Bundle options) {
+    public static boolean startActivities(@NonNull Context context, @NonNull Intent[] intents,
+            @Nullable Bundle options) {
         if (Build.VERSION.SDK_INT >= 16) {
             context.startActivities(intents, options);
         } else {
@@ -136,7 +137,8 @@
      *                supplied here; there are no supported definitions for
      *                building it manually.
      */
-    public static void startActivity(Context context, Intent intent, @Nullable Bundle options) {
+    public static void startActivity(@NonNull Context context, @NonNull Intent intent,
+            @Nullable Bundle options) {
         if (Build.VERSION.SDK_INT >= 16) {
             context.startActivity(intent, options);
         } else {
@@ -159,7 +161,8 @@
      *
      * @see ApplicationInfo#dataDir
      */
-    public static File getDataDir(Context context) {
+    @Nullable
+    public static File getDataDir(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 24) {
             return context.getDataDir();
         } else {
@@ -211,7 +214,8 @@
      * @see Context#getObbDir()
      * @see EnvironmentCompat#getStorageState(File)
      */
-    public static File[] getObbDirs(Context context) {
+    @NonNull
+    public static File[] getObbDirs(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 19) {
             return context.getObbDirs();
         } else {
@@ -263,7 +267,8 @@
      * @see Context#getExternalFilesDir(String)
      * @see EnvironmentCompat#getStorageState(File)
      */
-    public static File[] getExternalFilesDirs(Context context, String type) {
+    @NonNull
+    public static File[] getExternalFilesDirs(@NonNull Context context, @Nullable String type) {
         if (Build.VERSION.SDK_INT >= 19) {
             return context.getExternalFilesDirs(type);
         } else {
@@ -315,7 +320,8 @@
      * @see Context#getExternalCacheDir()
      * @see EnvironmentCompat#getStorageState(File)
      */
-    public static File[] getExternalCacheDirs(Context context) {
+    @NonNull
+    public static File[] getExternalCacheDirs(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 19) {
             return context.getExternalCacheDirs();
         } else {
@@ -346,7 +352,8 @@
      *           The value 0 is an invalid identifier.
      * @return Drawable An object that can be used to draw this resource.
      */
-    public static final Drawable getDrawable(Context context, @DrawableRes int id) {
+    @Nullable
+    public static final Drawable getDrawable(@NonNull Context context, @DrawableRes int id) {
         if (Build.VERSION.SDK_INT >= 21) {
             return context.getDrawable(id);
         } else if (Build.VERSION.SDK_INT >= 16) {
@@ -382,7 +389,9 @@
      * @throws android.content.res.Resources.NotFoundException if the given ID
      *         does not exist.
      */
-    public static final ColorStateList getColorStateList(Context context, @ColorRes int id) {
+    @Nullable
+    public static final ColorStateList getColorStateList(@NonNull Context context,
+            @ColorRes int id) {
         if (Build.VERSION.SDK_INT >= 23) {
             return context.getColorStateList(id);
         } else {
@@ -404,7 +413,7 @@
      *         does not exist.
      */
     @ColorInt
-    public static final int getColor(Context context, @ColorRes int id) {
+    public static final int getColor(@NonNull Context context, @ColorRes int id) {
         if (Build.VERSION.SDK_INT >= 23) {
             return context.getColor(id);
         } else {
@@ -444,7 +453,8 @@
      *
      * @see android.content.Context#getFilesDir()
      */
-    public static final File getNoBackupFilesDir(Context context) {
+    @Nullable
+    public static final File getNoBackupFilesDir(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 21) {
             return context.getNoBackupFilesDir();
         } else {
@@ -468,7 +478,7 @@
      *
      * @return The path of the directory holding application code cache files.
      */
-    public static File getCodeCacheDir(Context context) {
+    public static File getCodeCacheDir(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 21) {
             return context.getCodeCacheDir();
         } else {
@@ -522,7 +532,8 @@
      *
      * @see ContextCompat#isDeviceProtectedStorage(Context)
      */
-    public static Context createDeviceProtectedStorageContext(Context context) {
+    @Nullable
+    public static Context createDeviceProtectedStorageContext(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 24) {
             return context.createDeviceProtectedStorageContext();
         } else {
@@ -536,7 +547,7 @@
      *
      * @see ContextCompat#createDeviceProtectedStorageContext(Context)
      */
-    public static boolean isDeviceProtectedStorage(Context context) {
+    public static boolean isDeviceProtectedStorage(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= 24) {
             return context.isDeviceProtectedStorage();
         } else {
@@ -554,7 +565,7 @@
      * @see Context#startForegeroundService()
      * @see Context#startService()
      */
-    public static void startForegroundService(Context context, Intent intent) {
+    public static void startForegroundService(@NonNull Context context, @NonNull Intent intent) {
         if (Build.VERSION.SDK_INT >= 26) {
             context.startForegroundService(intent);
         } else {
diff --git a/android/support/v4/content/CursorLoader.java b/android/support/v4/content/CursorLoader.java
index 503bb9c..5c6925d 100644
--- a/android/support/v4/content/CursorLoader.java
+++ b/android/support/v4/content/CursorLoader.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.os.CancellationSignal;
 import android.support.v4.os.OperationCanceledException;
 
@@ -115,7 +117,7 @@
      * calls to {@link #setUri(Uri)}, {@link #setSelection(String)}, etc
      * to specify the query to perform.
      */
-    public CursorLoader(Context context) {
+    public CursorLoader(@NonNull Context context) {
         super(context);
         mObserver = new ForceLoadContentObserver();
     }
@@ -126,8 +128,9 @@
      * ContentResolver.query()} for documentation on the meaning of the
      * parameters.  These will be passed as-is to that call.
      */
-    public CursorLoader(Context context, Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
+    public CursorLoader(@NonNull Context context, @NonNull Uri uri, @Nullable String[] projection,
+            @Nullable String selection, @Nullable String[] selectionArgs,
+            @Nullable String sortOrder) {
         super(context);
         mObserver = new ForceLoadContentObserver();
         mUri = uri;
@@ -183,43 +186,48 @@
         mCursor = null;
     }
 
+    @NonNull
     public Uri getUri() {
         return mUri;
     }
 
-    public void setUri(Uri uri) {
+    public void setUri(@NonNull Uri uri) {
         mUri = uri;
     }
 
+    @Nullable
     public String[] getProjection() {
         return mProjection;
     }
 
-    public void setProjection(String[] projection) {
+    public void setProjection(@Nullable String[] projection) {
         mProjection = projection;
     }
 
+    @Nullable
     public String getSelection() {
         return mSelection;
     }
 
-    public void setSelection(String selection) {
+    public void setSelection(@Nullable String selection) {
         mSelection = selection;
     }
 
+    @Nullable
     public String[] getSelectionArgs() {
         return mSelectionArgs;
     }
 
-    public void setSelectionArgs(String[] selectionArgs) {
+    public void setSelectionArgs(@Nullable String[] selectionArgs) {
         mSelectionArgs = selectionArgs;
     }
 
+    @Nullable
     public String getSortOrder() {
         return mSortOrder;
     }
 
-    public void setSortOrder(String sortOrder) {
+    public void setSortOrder(@Nullable String sortOrder) {
         mSortOrder = sortOrder;
     }
 
diff --git a/android/support/v4/content/FileProvider.java b/android/support/v4/content/FileProvider.java
index c49fc12..8599911 100644
--- a/android/support/v4/content/FileProvider.java
+++ b/android/support/v4/content/FileProvider.java
@@ -33,6 +33,8 @@
 import android.os.ParcelFileDescriptor;
 import android.provider.OpenableColumns;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.webkit.MimeTypeMap;
 
@@ -362,7 +364,7 @@
      * @param info A {@link ProviderInfo} for the new provider.
      */
     @Override
-    public void attachInfo(Context context, ProviderInfo info) {
+    public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
         super.attachInfo(context, info);
 
         // Sanity check our security
@@ -396,7 +398,8 @@
      * @throws IllegalArgumentException When the given {@link File} is outside
      * the paths supported by the provider.
      */
-    public static Uri getUriForFile(Context context, String authority, File file) {
+    public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
+            @NonNull File file) {
         final PathStrategy strategy = getPathStrategy(context, authority);
         return strategy.getUriForFile(file);
     }
@@ -430,8 +433,9 @@
      *
      */
     @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
+    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+            @Nullable String[] selectionArgs,
+            @Nullable String sortOrder) {
         // ContentProvider has already checked granted permissions
         final File file = mStrategy.getFileForUri(uri);
 
@@ -470,7 +474,7 @@
      * extension; otherwise <code>application/octet-stream</code>.
      */
     @Override
-    public String getType(Uri uri) {
+    public String getType(@NonNull Uri uri) {
         // ContentProvider has already checked granted permissions
         final File file = mStrategy.getFileForUri(uri);
 
@@ -491,7 +495,7 @@
      * subclass FileProvider if you want to provide different functionality.
      */
     @Override
-    public Uri insert(Uri uri, ContentValues values) {
+    public Uri insert(@NonNull Uri uri, ContentValues values) {
         throw new UnsupportedOperationException("No external inserts");
     }
 
@@ -500,7 +504,8 @@
      * subclass FileProvider if you want to provide different functionality.
      */
     @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+    public int update(@NonNull Uri uri, ContentValues values, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
         throw new UnsupportedOperationException("No external updates");
     }
 
@@ -516,7 +521,8 @@
      * @return 1 if the delete succeeds; otherwise, 0.
      */
     @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
+    public int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
         // ContentProvider has already checked granted permissions
         final File file = mStrategy.getFileForUri(uri);
         return file.delete() ? 1 : 0;
@@ -538,7 +544,8 @@
      * @return A new {@link ParcelFileDescriptor} with which you can access the file.
      */
     @Override
-    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
+            throws FileNotFoundException {
         // ContentProvider has already checked granted permissions
         final File file = mStrategy.getFileForUri(uri);
         final int fileMode = modeToMode(mode);
diff --git a/android/support/v4/content/IntentCompat.java b/android/support/v4/content/IntentCompat.java
index 1a7295b..f5b3a0b 100644
--- a/android/support/v4/content/IntentCompat.java
+++ b/android/support/v4/content/IntentCompat.java
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 import android.os.Build;
+import android.support.annotation.NonNull;
 
 /**
  * Helper for accessing features in {@link android.content.Intent}.
@@ -69,8 +70,9 @@
      * @return Returns a newly created Intent that can be used to launch the
      * activity as a main application entry.
      */
-    public static Intent makeMainSelectorActivity(String selectorAction,
-            String selectorCategory) {
+    @NonNull
+    public static Intent makeMainSelectorActivity(@NonNull String selectorAction,
+            @NonNull String selectorCategory) {
         if (Build.VERSION.SDK_INT >= 15) {
             return Intent.makeMainSelectorActivity(selectorAction, selectorCategory);
         } else {
diff --git a/android/support/v4/content/Loader.java b/android/support/v4/content/Loader.java
index 40b459f..2ac10d7 100644
--- a/android/support/v4/content/Loader.java
+++ b/android/support/v4/content/Loader.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.util.DebugUtils;
 
 import java.io.FileDescriptor;
@@ -80,7 +82,7 @@
          * @param loader the loader that completed the load
          * @param data the result of the load
          */
-        public void onLoadComplete(Loader<D> loader, D data);
+        void onLoadComplete(@NonNull Loader<D> loader, @Nullable D data);
     }
 
     /**
@@ -97,7 +99,7 @@
          *
          * @param loader the loader that canceled the load
          */
-        public void onLoadCanceled(Loader<D> loader);
+        void onLoadCanceled(@NonNull Loader<D> loader);
     }
 
     /**
@@ -110,7 +112,7 @@
      *
      * @param context used to retrieve the application context.
      */
-    public Loader(Context context) {
+    public Loader(@NonNull Context context) {
         mContext = context.getApplicationContext();
     }
 
@@ -121,7 +123,7 @@
      *
      * @param data the result of the load
      */
-    public void deliverResult(D data) {
+    public void deliverResult(@Nullable D data) {
         if (mListener != null) {
             mListener.onLoadComplete(this, data);
         }
@@ -142,6 +144,7 @@
     /**
      * @return an application context retrieved from the Context passed to the constructor.
      */
+    @NonNull
     public Context getContext() {
         return mContext;
     }
@@ -160,7 +163,7 @@
      *
      * <p>Must be called from the process's main thread.
      */
-    public void registerListener(int id, OnLoadCompleteListener<D> listener) {
+    public void registerListener(int id, @NonNull OnLoadCompleteListener<D> listener) {
         if (mListener != null) {
             throw new IllegalStateException("There is already a listener registered");
         }
@@ -173,7 +176,7 @@
      *
      * Must be called from the process's main thread.
      */
-    public void unregisterListener(OnLoadCompleteListener<D> listener) {
+    public void unregisterListener(@NonNull OnLoadCompleteListener<D> listener) {
         if (mListener == null) {
             throw new IllegalStateException("No listener register");
         }
@@ -192,7 +195,7 @@
      *
      * @param listener The listener to register.
      */
-    public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+    public void registerOnLoadCanceledListener(@NonNull OnLoadCanceledListener<D> listener) {
         if (mOnLoadCanceledListener != null) {
             throw new IllegalStateException("There is already a listener registered");
         }
@@ -207,7 +210,7 @@
      *
      * @param listener The listener to unregister.
      */
-    public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+    public void unregisterOnLoadCanceledListener(@NonNull OnLoadCanceledListener<D> listener) {
         if (mOnLoadCanceledListener == null) {
             throw new IllegalStateException("No listener register");
         }
@@ -493,7 +496,8 @@
      * For debugging, converts an instance of the Loader's data class to
      * a string that can be printed.  Must handle a null data.
      */
-    public String dataToString(D data) {
+    @NonNull
+    public String dataToString(@Nullable D data) {
         StringBuilder sb = new StringBuilder(64);
         DebugUtils.buildShortClassTag(data, sb);
         sb.append("}");
diff --git a/android/support/v4/content/LocalBroadcastManager.java b/android/support/v4/content/LocalBroadcastManager.java
index 324bb30..aaaf8be 100644
--- a/android/support/v4/content/LocalBroadcastManager.java
+++ b/android/support/v4/content/LocalBroadcastManager.java
@@ -16,10 +16,6 @@
 
 package android.support.v4.content;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Set;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -27,8 +23,13 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
+import android.support.annotation.NonNull;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+
 /**
  * Helper to register for and send broadcasts of Intents to local objects
  * within your process.  This has a number of advantages over sending
@@ -98,7 +99,8 @@
     private static final Object mLock = new Object();
     private static LocalBroadcastManager mInstance;
 
-    public static LocalBroadcastManager getInstance(Context context) {
+    @NonNull
+    public static LocalBroadcastManager getInstance(@NonNull Context context) {
         synchronized (mLock) {
             if (mInstance == null) {
                 mInstance = new LocalBroadcastManager(context.getApplicationContext());
@@ -132,7 +134,8 @@
      *
      * @see #unregisterReceiver
      */
-    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+    public void registerReceiver(@NonNull BroadcastReceiver receiver,
+            @NonNull IntentFilter filter) {
         synchronized (mReceivers) {
             ReceiverRecord entry = new ReceiverRecord(filter, receiver);
             ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
@@ -162,7 +165,7 @@
      *
      * @see #registerReceiver
      */
-    public void unregisterReceiver(BroadcastReceiver receiver) {
+    public void unregisterReceiver(@NonNull BroadcastReceiver receiver) {
         synchronized (mReceivers) {
             final ArrayList<ReceiverRecord> filters = mReceivers.remove(receiver);
             if (filters == null) {
@@ -205,7 +208,7 @@
      * broadcast receivers.  (Note tha delivery may not ultimately take place if one of those
      * receivers is unregistered before it is dispatched.)
      */
-    public boolean sendBroadcast(Intent intent) {
+    public boolean sendBroadcast(@NonNull Intent intent) {
         synchronized (mReceivers) {
             final String action = intent.getAction();
             final String type = intent.resolveTypeIfNeeded(
@@ -281,7 +284,7 @@
      * the Intent this function will block and immediately dispatch them before
      * returning.
      */
-    public void sendBroadcastSync(Intent intent) {
+    public void sendBroadcastSync(@NonNull Intent intent) {
         if (sendBroadcast(intent)) {
             executePendingBroadcasts();
         }
diff --git a/android/support/v4/content/MimeTypeFilter.java b/android/support/v4/content/MimeTypeFilter.java
index 8734c4d..8a90c62 100644
--- a/android/support/v4/content/MimeTypeFilter.java
+++ b/android/support/v4/content/MimeTypeFilter.java
@@ -87,6 +87,7 @@
      * Matches one nullable MIME type against an array of MIME type filters.
      * @return The first matching filter, or null if nothing matches.
      */
+    @Nullable
     public static String matches(
             @Nullable String mimeType, @NonNull String[] filters) {
         if (mimeType == null) {
@@ -108,6 +109,7 @@
      * Matches multiple MIME types against an array of MIME type filters.
      * @return The first matching MIME type, or null if nothing matches.
      */
+    @Nullable
     public static String matches(
             @Nullable String[] mimeTypes, @NonNull String filter) {
         if (mimeTypes == null) {
@@ -129,6 +131,7 @@
      * Matches multiple MIME types against an array of MIME type filters.
      * @return The list of matching MIME types, or empty array if nothing matches.
      */
+    @NonNull
     public static String[] matchesMany(
             @Nullable String[] mimeTypes, @NonNull String filter) {
         if (mimeTypes == null) {
diff --git a/android/support/v4/content/PermissionChecker.java b/android/support/v4/content/PermissionChecker.java
index 0866273..c9f18a9 100644
--- a/android/support/v4/content/PermissionChecker.java
+++ b/android/support/v4/content/PermissionChecker.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.app.AppOpsManagerCompat;
 
@@ -91,7 +92,7 @@
      */
     @PermissionResult
     public static int checkPermission(@NonNull Context context, @NonNull String permission,
-            int pid, int uid, String packageName) {
+            int pid, int uid, @Nullable String packageName) {
         if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
             return PERMISSION_DENIED;
         }
@@ -146,7 +147,7 @@
      */
     @PermissionResult
     public static int checkCallingPermission(@NonNull Context context,
-            @NonNull String permission, String packageName) {
+            @NonNull String permission, @Nullable String packageName) {
         if (Binder.getCallingPid() == Process.myPid()) {
             return PERMISSION_DENIED;
         }
diff --git a/android/support/v4/content/SharedPreferencesCompat.java b/android/support/v4/content/SharedPreferencesCompat.java
index 7d51fed..1d43c2e 100644
--- a/android/support/v4/content/SharedPreferencesCompat.java
+++ b/android/support/v4/content/SharedPreferencesCompat.java
@@ -19,8 +19,18 @@
 import android.content.SharedPreferences;
 import android.support.annotation.NonNull;
 
+/**
+ * @deprecated This compatibility class is no longer required. Use {@link SharedPreferences}
+ * directly.
+ */
+@Deprecated
 public final class SharedPreferencesCompat {
 
+    /**
+     * @deprecated This compatibility class is no longer required. Use
+     * {@link SharedPreferences.Editor} directly.
+     */
+    @Deprecated
     public final static class EditorCompat {
 
         private static EditorCompat sInstance;
@@ -46,14 +56,22 @@
         private EditorCompat() {
             mHelper = new Helper();
         }
-
+        /**
+         * @deprecated This compatibility class is no longer required. Use
+         * {@link SharedPreferences.Editor} directly.
+         */
+        @Deprecated
         public static EditorCompat getInstance() {
             if (sInstance == null) {
                 sInstance = new EditorCompat();
             }
             return sInstance;
         }
-
+        /**
+         * @deprecated This compatibility method is no longer required. Use
+         * {@link SharedPreferences.Editor#apply()} directly.
+         */
+        @Deprecated
         public void apply(@NonNull SharedPreferences.Editor editor) {
             // Note that this redirection is needed to not break the public API chain
             // of getInstance().apply() calls. Otherwise this method could (and should)
diff --git a/android/support/v4/content/WakefulBroadcastReceiver.java b/android/support/v4/content/WakefulBroadcastReceiver.java
index b0cd653..8ec3eee 100644
--- a/android/support/v4/content/WakefulBroadcastReceiver.java
+++ b/android/support/v4/content/WakefulBroadcastReceiver.java
@@ -45,7 +45,7 @@
  * {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
  * holds an extra identifying the wake lock.</p>
  *
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulReceiver.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/content/SimpleWakefulReceiver.java
  *      complete}
  *
  * <p>The service (in this example, an {@link android.app.IntentService}) does
@@ -55,7 +55,7 @@
  * is the same intent that the {@link WakefulBroadcastReceiver} originally
  * passed in.</p>
  *
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulService.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/content/SimpleWakefulService.java
  *      complete}
  *
  * @deprecated As of {@link android.os.Build.VERSION_CODES#O Android O}, background check
diff --git a/android/support/v4/content/pm/ShortcutInfoCompat.java b/android/support/v4/content/pm/ShortcutInfoCompat.java
index 3ae7470..63585e1 100644
--- a/android/support/v4/content/pm/ShortcutInfoCompat.java
+++ b/android/support/v4/content/pm/ShortcutInfoCompat.java
@@ -18,17 +18,20 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.graphics.drawable.IconCompat;
 import android.text.TextUtils;
 
 import java.util.Arrays;
 
 /**
- * Helper for accessing features in {@link android.content.pm.ShortcutInfo}.
+ * Helper for accessing features in {@link ShortcutInfo}.
  */
 public class ShortcutInfoCompat {
 
@@ -43,6 +46,7 @@
     private CharSequence mDisabledMessage;
 
     private IconCompat mIcon;
+    private boolean mIsAlwaysBadged;
 
     private ShortcutInfoCompat() { }
 
@@ -69,11 +73,26 @@
         return builder.build();
     }
 
+    @VisibleForTesting
     Intent addToIntent(Intent outIntent) {
         outIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, mIntents[mIntents.length - 1])
                 .putExtra(Intent.EXTRA_SHORTCUT_NAME, mLabel.toString());
         if (mIcon != null) {
-            mIcon.addToShortcutIntent(outIntent);
+            Drawable badge = null;
+            if (mIsAlwaysBadged) {
+                PackageManager pm = mContext.getPackageManager();
+                if (mActivity != null) {
+                    try {
+                        badge = pm.getActivityIcon(mActivity);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // Ignore
+                    }
+                }
+                if (badge == null) {
+                    badge = mContext.getApplicationInfo().loadIcon(pm);
+                }
+            }
+            mIcon.addToShortcutIntent(outIntent, badge);
         }
         return outIntent;
     }
@@ -250,7 +269,7 @@
          * on the launcher.
          *
          * @see ShortcutInfo#getActivity()
-         * @see android.content.pm.ShortcutInfo.Builder#setActivity(ComponentName)
+         * @see ShortcutInfo.Builder#setActivity(ComponentName)
          */
         @NonNull
         public Builder setActivity(@NonNull ComponentName activity) {
@@ -259,6 +278,23 @@
         }
 
         /**
+         * Badges the icon before passing it over to the Launcher.
+         * <p>
+         * Launcher automatically badges {@link ShortcutInfo}, so only the legacy shortcut icon,
+         * {@link Intent.ShortcutIconResource} is badged. This field is ignored when using
+         * {@link ShortcutInfo} on API 25 and above.
+         * <p>
+         * If the shortcut is associated with an activity, the activity icon is used as the badge,
+         * otherwise application icon is used.
+         *
+         * @see #setActivity(ComponentName)
+         */
+        public Builder setAlwaysBadged() {
+            mInfo.mIsAlwaysBadged = true;
+            return this;
+        }
+
+        /**
          * Creates a {@link ShortcutInfoCompat} instance.
          */
         @NonNull
diff --git a/android/support/v4/content/res/FontResourcesParserCompat.java b/android/support/v4/content/res/FontResourcesParserCompat.java
index 7fe86ad..8ad07d3 100644
--- a/android/support/v4/content/res/FontResourcesParserCompat.java
+++ b/android/support/v4/content/res/FontResourcesParserCompat.java
@@ -252,10 +252,19 @@
             throws XmlPullParserException, IOException {
         AttributeSet attrs = Xml.asAttributeSet(parser);
         TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
-        int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT);
-        boolean isItalic = ITALIC == array.getInt(R.styleable.FontFamilyFont_fontStyle, 0);
-        int resourceId = array.getResourceId(R.styleable.FontFamilyFont_font, 0);
-        String filename = array.getString(R.styleable.FontFamilyFont_font);
+        final int weightAttr = array.hasValue(R.styleable.FontFamilyFont_fontWeight)
+                ? R.styleable.FontFamilyFont_fontWeight
+                : R.styleable.FontFamilyFont_android_fontWeight;
+        int weight = array.getInt(weightAttr, NORMAL_WEIGHT);
+        final int styleAttr = array.hasValue(R.styleable.FontFamilyFont_fontStyle)
+                ? R.styleable.FontFamilyFont_fontStyle
+                : R.styleable.FontFamilyFont_android_fontStyle;
+        boolean isItalic = ITALIC == array.getInt(styleAttr, 0);
+        final int resourceAttr = array.hasValue(R.styleable.FontFamilyFont_font)
+                ? R.styleable.FontFamilyFont_font
+                : R.styleable.FontFamilyFont_android_font;
+        int resourceId = array.getResourceId(resourceAttr, 0);
+        String filename = array.getString(resourceAttr);
         array.recycle();
         while (parser.next() != XmlPullParser.END_TAG) {
             skip(parser);
diff --git a/android/support/v4/content/res/ResourcesCompat.java b/android/support/v4/content/res/ResourcesCompat.java
index 43d78d0..4c70ce9 100644
--- a/android/support/v4/content/res/ResourcesCompat.java
+++ b/android/support/v4/content/res/ResourcesCompat.java
@@ -27,6 +27,8 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
 import android.support.annotation.ColorInt;
 import android.support.annotation.ColorRes;
 import android.support.annotation.DrawableRes;
@@ -36,9 +38,11 @@
 import android.support.annotation.RestrictTo;
 import android.support.v4.content.res.FontResourcesParserCompat.FamilyResourceEntry;
 import android.support.v4.graphics.TypefaceCompat;
+import android.support.v4.provider.FontsContractCompat.FontRequestCallback;
+import android.support.v4.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason;
+import android.support.v4.util.Preconditions;
 import android.util.Log;
 import android.util.TypedValue;
-import android.widget.TextView;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -175,8 +179,12 @@
     /**
      * Returns a font Typeface associated with a particular resource ID.
      * <p>
+     * This method will block the calling thread to retrieve the requested font, including if it
+     * is from a font provider. If you wish to not have this behavior, use
+     * {@link #getFont(Context, int, FontCallback, Handler)} instead.
+     * <p>
      * Prior to API level 23, font resources with more than one font in a family will only load the
-     * first font in that family.
+     * font closest to a regular weight typeface.
      *
      * @param context A context to retrieve the Resources from.
      * @param id The desired resource identifier of a {@link Typeface},
@@ -184,8 +192,9 @@
      *           package, type, and resource entry. The value 0 is an invalid
      *           identifier.
      * @return A font Typeface object.
-     * @throws NotFoundException Throws NotFoundException if the given ID does
-     *         not exist.
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     *
+     * @see #getFont(Context, int, FontCallback, Handler)
      */
     @Nullable
     public static Typeface getFont(@NonNull Context context, @FontRes int id)
@@ -193,34 +202,155 @@
         if (context.isRestricted()) {
             return null;
         }
-        return loadFont(context, id, new TypedValue(), Typeface.NORMAL, null);
+        return loadFont(context, id, new TypedValue(), Typeface.NORMAL, null /* callback */,
+                null /* handler */, false /* isXmlRequest */);
     }
 
-    /** @hide */
+    /**
+     * Interface used to receive asynchronous font fetching events.
+     */
+    public abstract static class FontCallback {
+
+        /**
+         * Called when an asynchronous font was finished loading.
+         *
+         * @param typeface The font that was loaded.
+         */
+        public abstract void onFontRetrieved(@NonNull Typeface typeface);
+
+        /**
+         * Called when an asynchronous font failed to load.
+         *
+         * @param reason The reason the font failed to load. One of
+         *      {@link FontRequestFailReason#FAIL_REASON_PROVIDER_NOT_FOUND},
+         *      {@link FontRequestFailReason#FAIL_REASON_WRONG_CERTIFICATES},
+         *      {@link FontRequestFailReason#FAIL_REASON_FONT_LOAD_ERROR},
+         *      {@link FontRequestFailReason#FAIL_REASON_SECURITY_VIOLATION},
+         *      {@link FontRequestFailReason#FAIL_REASON_FONT_NOT_FOUND},
+         *      {@link FontRequestFailReason#FAIL_REASON_FONT_UNAVAILABLE} or
+         *      {@link FontRequestFailReason#FAIL_REASON_MALFORMED_QUERY}.
+         */
+        public abstract void onFontRetrievalFailed(@FontRequestFailReason int reason);
+
+        /**
+         * Call {@link #onFontRetrieved(Typeface)} on the handler given, or the Ui Thread if it is
+         * null.
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public final void callbackSuccessAsync(final Typeface typeface, @Nullable Handler handler) {
+            if (handler == null) {
+                handler = new Handler(Looper.getMainLooper());
+            }
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    onFontRetrieved(typeface);
+                }
+            });
+        }
+
+        /**
+         * Call {@link #onFontRetrievalFailed(int)} on the handler given, or the Ui Thread if it is
+         * null.
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public final void callbackFailAsync(
+                @FontRequestFailReason final int reason, @Nullable Handler handler) {
+            if (handler == null) {
+                handler = new Handler(Looper.getMainLooper());
+            }
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    onFontRetrievalFailed(reason);
+                }
+            });
+        }
+    }
+
+    /**
+     * Returns a font Typeface associated with a particular resource ID asynchronously.
+     * <p>
+     * Prior to API level 23, font resources with more than one font in a family will only load the
+     * font closest to a regular weight typeface.
+     * </p>
+     *
+     * @param context A context to retrieve the Resources from.
+     * @param id The desired resource identifier of a {@link Typeface}, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource entry. The value 0 is an
+     *           invalid identifier.
+     * @param fontCallback A callback to receive async fetching of this font. The callback will be
+     *           triggered on the UI thread.
+     * @param handler A handler for the thread the callback should be called on. If null, the
+     *           callback will be called on the UI thread.
+     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+     */
+    public static void getFont(@NonNull Context context, @FontRes int id,
+            @NonNull FontCallback fontCallback, @Nullable Handler handler)
+            throws NotFoundException {
+        Preconditions.checkNotNull(fontCallback);
+        if (context.isRestricted()) {
+            fontCallback.callbackFailAsync(
+                    FontRequestCallback.FAIL_REASON_SECURITY_VIOLATION, handler);
+            return;
+        }
+        loadFont(context, id, new TypedValue(), Typeface.NORMAL, fontCallback, handler,
+                false /* isXmlRequest */);
+    }
+
+    /**
+     * Used by TintTypedArray.
+     *
+     * @hide
+     */
     @RestrictTo(LIBRARY_GROUP)
     public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
-            int style, @Nullable TextView targetView) throws NotFoundException {
+            int style) throws NotFoundException {
         if (context.isRestricted()) {
             return null;
         }
-        return loadFont(context, id, value, style, targetView);
+        return loadFont(context, id, value, style, null /* callback */, null /* handler */,
+                true /* isXmlRequest */);
     }
 
+    /**
+     *
+     * @param context The Context to get Resources from
+     * @param id The Resource id to load
+     * @param value A TypedValue to use in the fetching
+     * @param style The font style to load
+     * @param fontCallback A callback to trigger when the font is fetched or an error occurs
+     * @param handler A handler to the thread the callback should be called on
+     * @param isRequestFromLayoutInflator Whether this request originated from XML. This is used to
+     *                     determine if we use or ignore the fontProviderFetchStrategy attribute in
+     *                     font provider XML fonts.
+     * @return
+     */
     private static Typeface loadFont(@NonNull Context context, int id, TypedValue value,
-            int style, @Nullable TextView targetView) {
+            int style, @Nullable FontCallback fontCallback, @Nullable Handler handler,
+            boolean isRequestFromLayoutInflator) {
         final Resources resources = context.getResources();
         resources.getValue(id, value, true);
-        Typeface typeface = loadFont(context, resources, value, id, style, targetView);
-        if (typeface != null) {
-            return typeface;
+        Typeface typeface = loadFont(context, resources, value, id, style, fontCallback, handler,
+                isRequestFromLayoutInflator);
+        if (typeface == null && fontCallback == null) {
+            throw new NotFoundException("Font resource ID #0x"
+                    + Integer.toHexString(id) + " could not be retrieved.");
         }
-        throw new NotFoundException("Font resource ID #0x"
-                + Integer.toHexString(id));
+        return typeface;
     }
 
+    /**
+     * Load the given font. This method will always return null for asynchronous requests, which
+     * provide a fontCallback, as there is no immediate result. When the callback is not provided,
+     * the request is treated as synchronous and fails if async loading is required.
+     */
     private static Typeface loadFont(
             @NonNull Context context, Resources wrapper, TypedValue value, int id, int style,
-            @Nullable TextView targetView) {
+            @Nullable FontCallback fontCallback, @Nullable Handler handler,
+            boolean isRequestFromLayoutInflator) {
         if (value.string == null) {
             throw new NotFoundException("Resource \"" + wrapper.getResourceName(id) + "\" ("
                     + Integer.toHexString(id) + ") is not a Font: " + value);
@@ -228,13 +358,20 @@
 
         final String file = value.string.toString();
         if (!file.startsWith("res/")) {
-            // Early exit if the specified string is unlikely to the resource path.
+            // Early exit if the specified string is unlikely to be a resource path.
+            if (fontCallback != null) {
+                fontCallback.callbackFailAsync(
+                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+            }
             return null;
         }
+        Typeface typeface = TypefaceCompat.findFromCache(wrapper, id, style);
 
-        Typeface cached = TypefaceCompat.findFromCache(wrapper, id, style);
-        if (cached != null) {
-            return cached;
+        if (typeface != null) {
+            if (fontCallback != null) {
+                fontCallback.callbackSuccessAsync(typeface, handler);
+            }
+            return typeface;
         }
 
         try {
@@ -244,17 +381,35 @@
                         FontResourcesParserCompat.parse(rp, wrapper);
                 if (familyEntry == null) {
                     Log.e(TAG, "Failed to find font-family tag");
+                    if (fontCallback != null) {
+                        fontCallback.callbackFailAsync(
+                                FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+                    }
                     return null;
                 }
-                return TypefaceCompat.createFromResourcesFamilyXml(
-                        context, familyEntry, wrapper, id, style, targetView);
+                return TypefaceCompat.createFromResourcesFamilyXml(context, familyEntry, wrapper,
+                        id, style, fontCallback, handler, isRequestFromLayoutInflator);
             }
-            return TypefaceCompat.createFromResourcesFontFile(context, wrapper, id, file, style);
+            typeface = TypefaceCompat.createFromResourcesFontFile(
+                    context, wrapper, id, file, style);
+            if (fontCallback != null) {
+                if (typeface != null) {
+                    fontCallback.callbackSuccessAsync(typeface, handler);
+                } else {
+                    fontCallback.callbackFailAsync(
+                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+                }
+            }
+            return typeface;
         } catch (XmlPullParserException e) {
             Log.e(TAG, "Failed to parse xml resource " + file, e);
         } catch (IOException e) {
             Log.e(TAG, "Failed to read xml resource " + file, e);
         }
+        if (fontCallback != null) {
+            fontCallback.callbackFailAsync(
+                    FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+        }
         return null;
     }
 
diff --git a/android/support/v4/content/res/TypedArrayUtils.java b/android/support/v4/content/res/TypedArrayUtils.java
index e4d6b29..64cb981 100644
--- a/android/support/v4/content/res/TypedArrayUtils.java
+++ b/android/support/v4/content/res/TypedArrayUtils.java
@@ -24,6 +24,7 @@
 import android.support.annotation.AnyRes;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.StyleableRes;
 import android.util.AttributeSet;
@@ -80,7 +81,7 @@
      * {@code defaultValue} if it does not exist.
      */
     public static boolean getNamedBoolean(@NonNull TypedArray a, @NonNull XmlPullParser parser,
-            String attrName, @StyleableRes int resId, boolean defaultValue) {
+            @NonNull String attrName, @StyleableRes int resId, boolean defaultValue) {
         final boolean hasAttr = hasAttribute(parser, attrName);
         if (!hasAttr) {
             return defaultValue;
@@ -97,7 +98,7 @@
      * {@code defaultValue} if it does not exist.
      */
     public static int getNamedInt(@NonNull TypedArray a, @NonNull XmlPullParser parser,
-            String attrName, @StyleableRes int resId, int defaultValue) {
+            @NonNull String attrName, @StyleableRes int resId, int defaultValue) {
         final boolean hasAttr = hasAttribute(parser, attrName);
         if (!hasAttr) {
             return defaultValue;
@@ -115,7 +116,7 @@
      */
     @ColorInt
     public static int getNamedColor(@NonNull TypedArray a, @NonNull XmlPullParser parser,
-            String attrName, @StyleableRes int resId, @ColorInt int defaultValue) {
+            @NonNull String attrName, @StyleableRes int resId, @ColorInt int defaultValue) {
         final boolean hasAttr = hasAttribute(parser, attrName);
         if (!hasAttr) {
             return defaultValue;
@@ -133,7 +134,7 @@
      */
     @AnyRes
     public static int getNamedResourceId(@NonNull TypedArray a, @NonNull XmlPullParser parser,
-            String attrName, @StyleableRes int resId, @AnyRes int defaultValue) {
+            @NonNull String attrName, @StyleableRes int resId, @AnyRes int defaultValue) {
         final boolean hasAttr = hasAttribute(parser, attrName);
         if (!hasAttr) {
             return defaultValue;
@@ -149,8 +150,9 @@
      * @return a string value in the {@link TypedArray} with the specified {@code resId}, or
      * null if it does not exist.
      */
+    @Nullable
     public static String getNamedString(@NonNull TypedArray a, @NonNull XmlPullParser parser,
-            String attrName, @StyleableRes int resId) {
+            @NonNull String attrName, @StyleableRes int resId) {
         final boolean hasAttr = hasAttribute(parser, attrName);
         if (!hasAttr) {
             return null;
@@ -164,8 +166,9 @@
      * and return a temporary object holding its data.  This object is only
      * valid until the next call on to {@link TypedArray}.
      */
-    public static TypedValue peekNamedValue(TypedArray a, XmlPullParser parser, String attrName,
-            int resId) {
+    @Nullable
+    public static TypedValue peekNamedValue(@NonNull TypedArray a, @NonNull XmlPullParser parser,
+            @NonNull String attrName, int resId) {
         final boolean hasAttr = hasAttribute(parser, attrName);
         if (!hasAttr) {
             return null;
@@ -178,8 +181,9 @@
      * Obtains styled attributes from the theme, if available, or unstyled
      * resources if the theme is null.
      */
-    public static TypedArray obtainAttributes(
-            Resources res, Resources.Theme theme, AttributeSet set, int[] attrs) {
+    @NonNull
+    public static TypedArray obtainAttributes(@NonNull Resources res,
+            @Nullable Resources.Theme theme, @NonNull AttributeSet set, @NonNull int[] attrs) {
         if (theme == null) {
             return res.obtainAttributes(set, attrs);
         }
@@ -190,7 +194,7 @@
      * @return a boolean value of {@code index}. If it does not exist, a boolean value of
      * {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
      */
-    public static boolean getBoolean(TypedArray a, @StyleableRes int index,
+    public static boolean getBoolean(@NonNull TypedArray a, @StyleableRes int index,
             @StyleableRes int fallbackIndex, boolean defaultValue) {
         boolean val = a.getBoolean(fallbackIndex, defaultValue);
         return a.getBoolean(index, val);
@@ -200,7 +204,8 @@
      * @return a drawable value of {@code index}. If it does not exist, a drawable value of
      * {@code fallbackIndex}. If it still does not exist, {@code null}.
      */
-    public static Drawable getDrawable(TypedArray a, @StyleableRes int index,
+    @Nullable
+    public static Drawable getDrawable(@NonNull TypedArray a, @StyleableRes int index,
             @StyleableRes int fallbackIndex) {
         Drawable val = a.getDrawable(index);
         if (val == null) {
@@ -213,7 +218,7 @@
      * @return an int value of {@code index}. If it does not exist, an int value of
      * {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
      */
-    public static int getInt(TypedArray a, @StyleableRes int index,
+    public static int getInt(@NonNull TypedArray a, @StyleableRes int index,
             @StyleableRes int fallbackIndex, int defaultValue) {
         int val = a.getInt(fallbackIndex, defaultValue);
         return a.getInt(index, val);
@@ -224,7 +229,7 @@
      * {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
      */
     @AnyRes
-    public static int getResourceId(TypedArray a, @StyleableRes int index,
+    public static int getResourceId(@NonNull TypedArray a, @StyleableRes int index,
             @StyleableRes int fallbackIndex, @AnyRes int defaultValue) {
         int val = a.getResourceId(fallbackIndex, defaultValue);
         return a.getResourceId(index, val);
@@ -234,7 +239,8 @@
      * @return a string value of {@code index}. If it does not exist, a string value of
      * {@code fallbackIndex}. If it still does not exist, {@code null}.
      */
-    public static String getString(TypedArray a, @StyleableRes int index,
+    @Nullable
+    public static String getString(@NonNull TypedArray a, @StyleableRes int index,
             @StyleableRes int fallbackIndex) {
         String val = a.getString(index);
         if (val == null) {
@@ -249,7 +255,8 @@
      * @return a text value of {@code index}. If it does not exist, a text value of
      * {@code fallbackIndex}. If it still does not exist, {@code null}.
      */
-    public static CharSequence getText(TypedArray a, @StyleableRes int index,
+    @Nullable
+    public static CharSequence getText(@NonNull TypedArray a, @StyleableRes int index,
             @StyleableRes int fallbackIndex) {
         CharSequence val = a.getText(index);
         if (val == null) {
@@ -264,7 +271,8 @@
      * @return a string array value of {@code index}. If it does not exist, a string array value
      * of {@code fallbackIndex}. If it still does not exist, {@code null}.
      */
-    public static CharSequence[] getTextArray(TypedArray a, @StyleableRes int index,
+    @Nullable
+    public static CharSequence[] getTextArray(@NonNull TypedArray a, @StyleableRes int index,
             @StyleableRes int fallbackIndex) {
         CharSequence[] val = a.getTextArray(index);
         if (val == null) {
@@ -277,7 +285,7 @@
      * @return The resource ID value in the {@code context} specified by {@code attr}. If it does
      * not exist, {@code fallbackAttr}.
      */
-    public static int getAttr(Context context, int attr, int fallbackAttr) {
+    public static int getAttr(@NonNull Context context, int attr, int fallbackAttr) {
         TypedValue value = new TypedValue();
         context.getTheme().resolveAttribute(attr, value, true);
         if (value.resourceId != 0) {
diff --git a/android/support/v4/graphics/BitmapCompat.java b/android/support/v4/graphics/BitmapCompat.java
index 20caf80..acc37b1 100644
--- a/android/support/v4/graphics/BitmapCompat.java
+++ b/android/support/v4/graphics/BitmapCompat.java
@@ -17,6 +17,7 @@
 
 import android.graphics.Bitmap;
 import android.os.Build;
+import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
 
 /**
@@ -71,11 +72,11 @@
         }
     }
 
-    public static boolean hasMipMap(Bitmap bitmap) {
+    public static boolean hasMipMap(@NonNull Bitmap bitmap) {
         return IMPL.hasMipMap(bitmap);
     }
 
-    public static void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
+    public static void setHasMipMap(@NonNull Bitmap bitmap, boolean hasMipMap) {
         IMPL.setHasMipMap(bitmap, hasMipMap);
     }
 
@@ -86,7 +87,7 @@
      * @param bitmap the bitmap in which to return its allocation size
      * @return the allocation size in bytes
      */
-    public static int getAllocationByteCount(Bitmap bitmap) {
+    public static int getAllocationByteCount(@NonNull Bitmap bitmap) {
         return IMPL.getAllocationByteCount(bitmap);
     }
 
diff --git a/android/support/v4/graphics/TypefaceCompat.java b/android/support/v4/graphics/TypefaceCompat.java
index 6d114b6..3c55df6 100644
--- a/android/support/v4/graphics/TypefaceCompat.java
+++ b/android/support/v4/graphics/TypefaceCompat.java
@@ -23,16 +23,18 @@
 import android.graphics.Typeface;
 import android.os.Build;
 import android.os.CancellationSignal;
+import android.os.Handler;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.FontResourcesParserCompat;
 import android.support.v4.content.res.FontResourcesParserCompat.FamilyResourceEntry;
 import android.support.v4.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry;
 import android.support.v4.content.res.FontResourcesParserCompat.ProviderResourceEntry;
+import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.provider.FontsContractCompat;
 import android.support.v4.provider.FontsContractCompat.FontInfo;
 import android.support.v4.util.LruCache;
-import android.widget.TextView;
 
 /**
  * Helper for accessing features in {@link Typeface}.
@@ -82,7 +84,8 @@
      *
      * @return null if not found.
      */
-    public static Typeface findFromCache(Resources resources, int id, int style) {
+    @Nullable
+    public static Typeface findFromCache(@NonNull Resources resources, int id, int style) {
         return sTypefaceCache.get(createResourceUid(resources, id, style));
     }
 
@@ -103,18 +106,35 @@
      *
      * @return null if failed to create.
      */
+    @Nullable
     public static Typeface createFromResourcesFamilyXml(
-            Context context, FamilyResourceEntry entry, Resources resources, int id, int style,
-            @Nullable TextView targetView) {
+            @NonNull Context context, @NonNull FamilyResourceEntry entry,
+            @NonNull Resources resources, int id, int style,
+            @Nullable ResourcesCompat.FontCallback fontCallback, @Nullable Handler handler,
+            boolean isRequestFromLayoutInflator) {
         Typeface typeface;
         if (entry instanceof ProviderResourceEntry) {
             ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
-            typeface = FontsContractCompat.getFontSync(context,
-                    providerEntry.getRequest(), targetView, providerEntry.getFetchStrategy(),
-                    providerEntry.getTimeout(), style);
+            final boolean isBlocking = isRequestFromLayoutInflator
+                    ? providerEntry.getFetchStrategy()
+                    == FontResourcesParserCompat.FETCH_STRATEGY_BLOCKING
+                    : fontCallback == null;
+            final int timeout = isRequestFromLayoutInflator ? providerEntry.getTimeout()
+                    : FontResourcesParserCompat.INFINITE_TIMEOUT_VALUE;
+            typeface = FontsContractCompat.getFontSync(context, providerEntry.getRequest(),
+                    fontCallback, handler, isBlocking, timeout, style);
         } else {
             typeface = sTypefaceCompatImpl.createFromFontFamilyFilesResourceEntry(
                     context, (FontFamilyFilesResourceEntry) entry, resources, style);
+            if (fontCallback != null) {
+                if (typeface != null) {
+                    fontCallback.callbackSuccessAsync(typeface, handler);
+                } else {
+                    fontCallback.callbackFailAsync(
+                            FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR,
+                            handler);
+                }
+            }
         }
         if (typeface != null) {
             sTypefaceCache.put(createResourceUid(resources, id, style), typeface);
@@ -127,11 +147,13 @@
      */
     @Nullable
     public static Typeface createFromResourcesFontFile(
-            Context context, Resources resources, int id, String path, int style) {
+            @NonNull Context context, @NonNull Resources resources, int id, String path,
+            int style) {
         Typeface typeface = sTypefaceCompatImpl.createFromResourcesFontFile(
                 context, resources, id, path, style);
         if (typeface != null) {
-            sTypefaceCache.put(createResourceUid(resources, id, style), typeface);
+            final String resourceUid = createResourceUid(resources, id, style);
+            sTypefaceCache.put(resourceUid, typeface);
         }
         return typeface;
     }
@@ -139,7 +161,8 @@
     /**
      * Create a Typeface from a given FontInfo list and a map that matches them to ByteBuffers.
      */
-    public static Typeface createFromFontInfo(Context context,
+    @Nullable
+    public static Typeface createFromFontInfo(@NonNull Context context,
             @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts, int style) {
         return sTypefaceCompatImpl.createFromFontInfo(context, cancellationSignal, fonts, style);
     }
diff --git a/android/support/v4/graphics/TypefaceCompatApi24Impl.java b/android/support/v4/graphics/TypefaceCompatApi24Impl.java
index a107859..89a6ec4 100644
--- a/android/support/v4/graphics/TypefaceCompatApi24Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi24Impl.java
@@ -145,7 +145,8 @@
                 return null;
             }
         }
-        return createFromFamiliesWithDefault(family);
+        final Typeface typeface = createFromFamiliesWithDefault(family);
+        return Typeface.create(typeface, style);
     }
 
     @Override
diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index c7066c8..1b55a2e 100644
--- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -118,7 +118,7 @@
      */
     private static boolean isFontFamilyPrivateAPIAvailable() {
         if (sAddFontFromAssetManager == null) {
-            Log.w(TAG, "Unable to collect necessary private methods."
+            Log.w(TAG, "Unable to collect necessary private methods. "
                     + "Fallback to legacy implementation.");
         }
         return sAddFontFromAssetManager != null;
@@ -273,7 +273,8 @@
         if (!freeze(fontFamily)) {
             return null;
         }
-        return createFromFamiliesWithDefault(fontFamily);
+        final Typeface typeface = createFromFamiliesWithDefault(fontFamily);
+        return Typeface.create(typeface, style);
     }
 
     /**
diff --git a/android/support/v4/graphics/drawable/IconCompat.java b/android/support/v4/graphics/drawable/IconCompat.java
index 0a99fed..359c96b 100644
--- a/android/support/v4/graphics/drawable/IconCompat.java
+++ b/android/support/v4/graphics/drawable/IconCompat.java
@@ -18,6 +18,7 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -27,13 +28,17 @@
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Build;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.VisibleForTesting;
+import android.support.v4.content.ContextCompat;
 
 /**
  * Helper for accessing features in {@link android.graphics.drawable.Icon}.
@@ -202,25 +207,63 @@
     }
 
     /**
+     * Use {@link #addToShortcutIntent(Intent, Drawable)} instead
      * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
-    public void addToShortcutIntent(Intent outIntent) {
+    @Deprecated
+    public void addToShortcutIntent(@NonNull Intent outIntent) {
+        addToShortcutIntent(outIntent, null);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge) {
+        Bitmap icon;
         switch (mType) {
             case TYPE_BITMAP:
-                outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, (Bitmap) mObj1);
+                icon = (Bitmap) mObj1;
+                if (badge != null) {
+                    // Do not modify the original icon when applying a badge
+                    icon = icon.copy(icon.getConfig(), true);
+                }
                 break;
             case TYPE_ADAPTIVE_BITMAP:
-                outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
-                        createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true));
+                icon = createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true);
                 break;
             case TYPE_RESOURCE:
-                outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
-                        Intent.ShortcutIconResource.fromContext((Context) mObj1, mInt1));
+                if (badge == null) {
+                    outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+                            Intent.ShortcutIconResource.fromContext((Context) mObj1, mInt1));
+                    return;
+                } else {
+                    Context context = (Context) mObj1;
+                    Drawable dr = ContextCompat.getDrawable(context, mInt1);
+                    if (dr.getIntrinsicWidth() <= 0 || dr.getIntrinsicHeight() <= 0) {
+                        int size = ((ActivityManager) context.getSystemService(
+                                Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize();
+                        icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+                    } else {
+                        icon = Bitmap.createBitmap(dr.getIntrinsicWidth(), dr.getIntrinsicHeight(),
+                                Bitmap.Config.ARGB_8888);
+                    }
+                    dr.setBounds(0, 0, icon.getWidth(), icon.getHeight());
+                    dr.draw(new Canvas(icon));
+                }
                 break;
             default:
                 throw new IllegalArgumentException("Icon type not supported for intent shortcuts");
         }
+        if (badge != null) {
+            // Badge the icon
+            int w = icon.getWidth();
+            int h = icon.getHeight();
+            badge.setBounds(w / 2, h / 2, w, h);
+            badge.draw(new Canvas(icon));
+        }
+        outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
     }
 
     /**
diff --git a/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java b/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
index d515561..795126d 100644
--- a/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
+++ b/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
@@ -27,6 +27,8 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.util.DisplayMetrics;
 import android.view.Gravity;
@@ -66,6 +68,7 @@
     /**
      * Returns the paint used to render this drawable.
      */
+    @NonNull
     public final Paint getPaint() {
         return mPaint;
     }
@@ -73,6 +76,7 @@
     /**
      * Returns the bitmap used by this drawable to render. May be null.
      */
+    @Nullable
     public final Bitmap getBitmap() {
         return mBitmap;
     }
@@ -92,7 +96,7 @@
      * @see android.graphics.Bitmap#setDensity(int)
      * @see android.graphics.Bitmap#getDensity()
      */
-    public void setTargetDensity(Canvas canvas) {
+    public void setTargetDensity(@NonNull Canvas canvas) {
         setTargetDensity(canvas.getDensity());
     }
 
@@ -104,7 +108,7 @@
      * @see android.graphics.Bitmap#setDensity(int)
      * @see android.graphics.Bitmap#getDensity()
      */
-    public void setTargetDensity(DisplayMetrics metrics) {
+    public void setTargetDensity(@NonNull DisplayMetrics metrics) {
         setTargetDensity(metrics.densityDpi);
     }
 
@@ -253,7 +257,7 @@
     }
 
     @Override
-    public void draw(Canvas canvas) {
+    public void draw(@NonNull Canvas canvas) {
         final Bitmap bitmap = mBitmap;
         if (bitmap == null) {
             return;
diff --git a/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java b/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
index 5e144c7..7790055 100644
--- a/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
+++ b/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
@@ -21,11 +21,15 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Rect;
 import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.graphics.BitmapCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.util.Log;
 
+import java.io.InputStream;
+
 /**
  * Constructs {@link RoundedBitmapDrawable RoundedBitmapDrawable} objects,
  * either from Bitmaps directly, or from streams and files.
@@ -63,7 +67,8 @@
      * Returns a new drawable by creating it from a bitmap, setting initial target density based on
      * the display metrics of the resources.
      */
-    public static RoundedBitmapDrawable create(Resources res, Bitmap bitmap) {
+    @NonNull
+    public static RoundedBitmapDrawable create(@NonNull Resources res, @Nullable Bitmap bitmap) {
         if (Build.VERSION.SDK_INT >= 21) {
             return new RoundedBitmapDrawable21(res, bitmap);
         }
@@ -73,8 +78,8 @@
     /**
      * Returns a new drawable, creating it by opening a given file path and decoding the bitmap.
      */
-    public static RoundedBitmapDrawable create(Resources res,
-            String filepath) {
+    @NonNull
+    public static RoundedBitmapDrawable create(@NonNull Resources res, @NonNull String filepath) {
         final RoundedBitmapDrawable drawable = create(res, BitmapFactory.decodeFile(filepath));
         if (drawable.getBitmap() == null) {
             Log.w(TAG, "RoundedBitmapDrawable cannot decode " + filepath);
@@ -86,8 +91,8 @@
     /**
      * Returns a new drawable, creating it by decoding a bitmap from the given input stream.
      */
-    public static RoundedBitmapDrawable create(Resources res,
-            java.io.InputStream is) {
+    @NonNull
+    public static RoundedBitmapDrawable create(@NonNull Resources res, @NonNull InputStream is) {
         final RoundedBitmapDrawable drawable = create(res, BitmapFactory.decodeStream(is));
         if (drawable.getBitmap() == null) {
             Log.w(TAG, "RoundedBitmapDrawable cannot decode " + is);
diff --git a/android/support/v4/hardware/display/DisplayManagerCompat.java b/android/support/v4/hardware/display/DisplayManagerCompat.java
index 50d246b..22d39ac 100644
--- a/android/support/v4/hardware/display/DisplayManagerCompat.java
+++ b/android/support/v4/hardware/display/DisplayManagerCompat.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.view.Display;
 import android.view.WindowManager;
@@ -52,7 +54,8 @@
     /**
      * Gets an instance of the display manager given the context.
      */
-    public static DisplayManagerCompat getInstance(Context context) {
+    @NonNull
+    public static DisplayManagerCompat getInstance(@NonNull Context context) {
         synchronized (sInstances) {
             DisplayManagerCompat instance = sInstances.get(context);
             if (instance == null) {
@@ -76,6 +79,7 @@
      * @param displayId The logical display id.
      * @return The display object, or null if there is no valid display with the given id.
      */
+    @Nullable
     public abstract Display getDisplay(int displayId);
 
     /**
@@ -83,6 +87,7 @@
      *
      * @return An array containing all displays.
      */
+    @NonNull
     public abstract Display[] getDisplays();
 
     /**
@@ -101,6 +106,7 @@
      *
      * @see #DISPLAY_CATEGORY_PRESENTATION
      */
+    @NonNull
     public abstract Display[] getDisplays(String category);
 
     private static class DisplayManagerCompatApi14Impl extends DisplayManagerCompat {
diff --git a/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java b/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
index 5e23c68..68f9476 100644
--- a/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
+++ b/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
@@ -44,7 +44,8 @@
     private final Context mContext;
 
     /** Get a {@link FingerprintManagerCompat} instance for a provided context. */
-    public static FingerprintManagerCompat from(Context context) {
+    @NonNull
+    public static FingerprintManagerCompat from(@NonNull Context context) {
         return new FingerprintManagerCompat(context);
     }
 
@@ -119,8 +120,9 @@
         }
     }
 
+    @Nullable
     @RequiresApi(23)
-    private static FingerprintManager getFingerprintManagerOrNull(Context context) {
+    private static FingerprintManager getFingerprintManagerOrNull(@NonNull Context context) {
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             return context.getSystemService(FingerprintManager.class);
         } else {
@@ -195,20 +197,20 @@
         private final Cipher mCipher;
         private final Mac mMac;
 
-        public CryptoObject(Signature signature) {
+        public CryptoObject(@NonNull Signature signature) {
             mSignature = signature;
             mCipher = null;
             mMac = null;
 
         }
 
-        public CryptoObject(Cipher cipher) {
+        public CryptoObject(@NonNull Cipher cipher) {
             mCipher = cipher;
             mSignature = null;
             mMac = null;
         }
 
-        public CryptoObject(Mac mac) {
+        public CryptoObject(@NonNull Mac mac) {
             mMac = mac;
             mCipher = null;
             mSignature = null;
@@ -218,18 +220,21 @@
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
          */
+        @Nullable
         public Signature getSignature() { return mSignature; }
 
         /**
          * Get {@link Cipher} object.
          * @return {@link Cipher} object or null if this doesn't contain one.
          */
+        @Nullable
         public Cipher getCipher() { return mCipher; }
 
         /**
          * Get {@link Mac} object.
          * @return {@link Mac} object or null if this doesn't contain one.
          */
+        @Nullable
         public Mac getMac() { return mMac; }
     }
 
diff --git a/android/support/v4/media/session/MediaControllerCompat.java b/android/support/v4/media/session/MediaControllerCompat.java
index cea4771..2509cd4 100644
--- a/android/support/v4/media/session/MediaControllerCompat.java
+++ b/android/support/v4/media/session/MediaControllerCompat.java
@@ -70,7 +70,7 @@
  * <li>{@link #getPlaybackState()}.{@link PlaybackStateCompat#getExtras() getExtras()}</li>
  * <li>{@link #isCaptioningEnabled()}</li>
  * <li>{@link #getRepeatMode()}</li>
- * <li>{@link #isShuffleModeEnabled()}</li>
+ * <li>{@link #getShuffleMode()}</li>
  * </ul></p>
  *
  * <div class="special reference">
@@ -439,18 +439,6 @@
     }
 
     /**
-     * Returns whether the shuffle mode is enabled for this session.
-     *
-     * @return {@code true} if the shuffle mode is enabled, {@code false} if it is disabled, not
-     *         set, or the session is not ready.
-     * @deprecated Use {@link #getShuffleMode} instead.
-     */
-    @Deprecated
-    public boolean isShuffleModeEnabled() {
-        return mImpl.isShuffleModeEnabled();
-    }
-
-    /**
      * Gets the shuffle mode for this session.
      *
      * @return The latest shuffle mode set to the session, or
@@ -760,16 +748,6 @@
         /**
          * Override to handle changes to the shuffle mode.
          *
-         * @param enabled {@code true} if the shuffle mode is enabled, {@code false} otherwise.
-         * @deprecated Use {@link #onShuffleModeChanged(int)} instead.
-         */
-        @Deprecated
-        public void onShuffleModeChanged(boolean enabled) {
-        }
-
-        /**
-         * Override to handle changes to the shuffle mode.
-         *
          * @param shuffleMode The shuffle mode. Must be one of the followings:
          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
@@ -963,12 +941,8 @@
             }
 
             @Override
-            public void onShuffleModeChangedDeprecated(boolean enabled) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(
-                            MessageHandler.MSG_UPDATE_SHUFFLE_MODE_DEPRECATED, enabled, null);
-                }
+            public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException {
+                // Do nothing.
             }
 
             @Override
@@ -1020,7 +994,6 @@
             private static final int MSG_UPDATE_EXTRAS = 7;
             private static final int MSG_DESTROYED = 8;
             private static final int MSG_UPDATE_REPEAT_MODE = 9;
-            private static final int MSG_UPDATE_SHUFFLE_MODE_DEPRECATED = 10;
             private static final int MSG_UPDATE_CAPTIONING_ENABLED = 11;
             private static final int MSG_UPDATE_SHUFFLE_MODE = 12;
             private static final int MSG_SESSION_READY = 13;
@@ -1058,9 +1031,6 @@
                     case MSG_UPDATE_REPEAT_MODE:
                         onRepeatModeChanged((int) msg.obj);
                         break;
-                    case MSG_UPDATE_SHUFFLE_MODE_DEPRECATED:
-                        onShuffleModeChanged((boolean) msg.obj);
-                        break;
                     case MSG_UPDATE_SHUFFLE_MODE:
                         onShuffleModeChanged((int) msg.obj);
                         break;
@@ -1264,15 +1234,6 @@
         /**
          * Sets the shuffle mode for this session.
          *
-         * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
-         * @deprecated Use {@link #setShuffleMode} instead.
-         */
-        @Deprecated
-        public abstract void setShuffleModeEnabled(boolean enabled);
-
-        /**
-         * Sets the shuffle mode for this session.
-         *
          * @param shuffleMode The shuffle mode. Must be one of the followings:
          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
@@ -1414,7 +1375,6 @@
         int getRatingType();
         boolean isCaptioningEnabled();
         int getRepeatMode();
-        boolean isShuffleModeEnabled();
         int getShuffleMode();
         long getFlags();
         PlaybackInfo getPlaybackInfo();
@@ -1610,16 +1570,6 @@
         }
 
         @Override
-        public boolean isShuffleModeEnabled() {
-            try {
-                return mBinder.isShuffleModeEnabledDeprecated();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in isShuffleModeEnabled.", e);
-            }
-            return false;
-        }
-
-        @Override
         public int getShuffleMode() {
             try {
                 return mBinder.getShuffleMode();
@@ -1899,15 +1849,6 @@
         }
 
         @Override
-        public void setShuffleModeEnabled(boolean enabled) {
-            try {
-                mBinder.setShuffleModeEnabledDeprecated(enabled);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in setShuffleModeEnabled.", e);
-            }
-        }
-
-        @Override
         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
             try {
                 mBinder.setShuffleMode(shuffleMode);
@@ -2122,18 +2063,6 @@
         }
 
         @Override
-        public boolean isShuffleModeEnabled() {
-            if (mExtraBinder != null) {
-                try {
-                    return mExtraBinder.isShuffleModeEnabledDeprecated();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in isShuffleModeEnabled.", e);
-                }
-            }
-            return false;
-        }
-
-        @Override
         public int getShuffleMode() {
             if (mExtraBinder != null) {
                 try {
@@ -2391,13 +2320,6 @@
         }
 
         @Override
-        public void setShuffleModeEnabled(boolean enabled) {
-            Bundle bundle = new Bundle();
-            bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED, enabled);
-            sendCustomAction(MediaSessionCompat.ACTION_SET_SHUFFLE_MODE_ENABLED, bundle);
-        }
-
-        @Override
         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
             Bundle bundle = new Bundle();
             bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE, shuffleMode);
diff --git a/android/support/v4/media/session/MediaSessionCompat.java b/android/support/v4/media/session/MediaSessionCompat.java
index ea5beda..8b91413 100644
--- a/android/support/v4/media/session/MediaSessionCompat.java
+++ b/android/support/v4/media/session/MediaSessionCompat.java
@@ -259,12 +259,6 @@
             "android.support.v4.media.session.action.SET_REPEAT_MODE";
 
     /**
-     * Custom action to invoke setShuffleModeEnabled() for the forward compatibility.
-     */
-    static final String ACTION_SET_SHUFFLE_MODE_ENABLED =
-            "android.support.v4.media.session.action.SET_SHUFFLE_MODE_ENABLED";
-
-    /**
      * Custom action to invoke setShuffleMode() for the forward compatibility.
      */
     static final String ACTION_SET_SHUFFLE_MODE =
@@ -321,13 +315,6 @@
             "android.support.v4.media.session.action.ARGUMENT_REPEAT_MODE";
 
     /**
-     * Argument for use with {@link #ACTION_SET_SHUFFLE_MODE_ENABLED} indicating that shuffle mode
-     * is enabled.
-     */
-    static final String ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED =
-            "android.support.v4.media.session.action.ARGUMENT_SHUFFLE_MODE_ENABLED";
-
-    /**
      * Argument for use with {@link #ACTION_SET_SHUFFLE_MODE} indicating shuffle mode.
      */
     static final String ACTION_ARGUMENT_SHUFFLE_MODE =
@@ -704,20 +691,6 @@
     /**
      * Sets the shuffle mode for this session.
      * <p>
-     * Note that if this method is not called before,
-     * {@link MediaControllerCompat#isShuffleModeEnabled} will return {@code false}.
-     *
-     * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
-     * @deprecated Use {@link #setShuffleMode} instead.
-     */
-    @Deprecated
-    public void setShuffleModeEnabled(boolean enabled) {
-        mImpl.setShuffleModeEnabled(enabled);
-    }
-
-    /**
-     * Sets the shuffle mode for this session.
-     * <p>
      * Note that if this method is not called before, {@link MediaControllerCompat#getShuffleMode}
      * will return {@link PlaybackStateCompat#SHUFFLE_MODE_NONE}.
      *
@@ -1134,20 +1107,6 @@
         /**
          * Override to handle the setting of the shuffle mode.
          * <p>
-         * You should call {@link #setShuffleModeEnabled} before the end of this method in order to
-         * notify the change to the {@link MediaControllerCompat}, or
-         * {@link MediaControllerCompat#isShuffleModeEnabled} could return an invalid value.
-         *
-         * @param enabled true when the shuffle mode is enabled, false otherwise.
-         * @deprecated Use {@link #onSetShuffleMode} instead.
-         */
-        @Deprecated
-        public void onSetShuffleModeEnabled(boolean enabled) {
-        }
-
-        /**
-         * Override to handle the setting of the shuffle mode.
-         * <p>
          * You should call {@link #setShuffleMode} before the end of this method in order to
          * notify the change to the {@link MediaControllerCompat}, or
          * {@link MediaControllerCompat#getShuffleMode} could return an invalid value.
@@ -1386,9 +1345,6 @@
                 } else if (action.equals(ACTION_SET_REPEAT_MODE)) {
                     int repeatMode = extras.getInt(ACTION_ARGUMENT_REPEAT_MODE);
                     Callback.this.onSetRepeatMode(repeatMode);
-                } else if (action.equals(ACTION_SET_SHUFFLE_MODE_ENABLED)) {
-                    boolean enabled = extras.getBoolean(ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED);
-                    Callback.this.onSetShuffleModeEnabled(enabled);
                 } else if (action.equals(ACTION_SET_SHUFFLE_MODE)) {
                     int shuffleMode = extras.getInt(ACTION_ARGUMENT_SHUFFLE_MODE);
                     Callback.this.onSetShuffleMode(shuffleMode);
@@ -1797,7 +1753,6 @@
         void setRatingType(@RatingCompat.Style int type);
         void setCaptioningEnabled(boolean enabled);
         void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
-        void setShuffleModeEnabled(boolean enabled);
         void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
         void setExtras(Bundle extras);
 
@@ -1844,7 +1799,6 @@
         boolean mCaptioningEnabled;
         @PlaybackStateCompat.RepeatMode int mRepeatMode;
         @PlaybackStateCompat.ShuffleMode int mShuffleMode;
-        boolean mShuffleModeEnabled;
         Bundle mExtras;
 
         int mVolumeType;
@@ -2254,14 +2208,6 @@
         }
 
         @Override
-        public void setShuffleModeEnabled(boolean enabled) {
-            if (mShuffleModeEnabled != enabled) {
-                mShuffleModeEnabled = enabled;
-                sendShuffleModeEnabled(enabled);
-            }
-        }
-
-        @Override
         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
             if (mShuffleMode != shuffleMode) {
                 mShuffleMode = shuffleMode;
@@ -2460,18 +2406,6 @@
             mControllerCallbacks.finishBroadcast();
         }
 
-        private void sendShuffleModeEnabled(boolean enabled) {
-            int size = mControllerCallbacks.beginBroadcast();
-            for (int i = size - 1; i >= 0; i--) {
-                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
-                try {
-                    cb.onShuffleModeChangedDeprecated(enabled);
-                } catch (RemoteException e) {
-                }
-            }
-            mControllerCallbacks.finishBroadcast();
-        }
-
         private void sendShuffleMode(int shuffleMode) {
             int size = mControllerCallbacks.beginBroadcast();
             for (int i = size - 1; i >= 0; i--) {
@@ -2695,8 +2629,8 @@
             }
 
             @Override
-            public void setShuffleModeEnabledDeprecated(boolean enabled) throws RemoteException {
-                postToHandler(MessageHandler.MSG_SET_SHUFFLE_MODE_ENABLED, enabled);
+            public void setShuffleModeEnabledRemoved(boolean enabled) throws RemoteException {
+                // Do nothing.
             }
 
             @Override
@@ -2783,8 +2717,8 @@
             }
 
             @Override
-            public boolean isShuffleModeEnabledDeprecated() {
-                return mShuffleModeEnabled;
+            public boolean isShuffleModeEnabledRemoved() {
+                return false;
             }
 
             @Override
@@ -2837,7 +2771,6 @@
             private static final int MSG_MEDIA_BUTTON = 21;
             private static final int MSG_SET_VOLUME = 22;
             private static final int MSG_SET_REPEAT_MODE = 23;
-            private static final int MSG_SET_SHUFFLE_MODE_ENABLED = 24;
             private static final int MSG_ADD_QUEUE_ITEM = 25;
             private static final int MSG_ADD_QUEUE_ITEM_AT = 26;
             private static final int MSG_REMOVE_QUEUE_ITEM = 27;
@@ -2978,9 +2911,6 @@
                     case MSG_SET_REPEAT_MODE:
                         cb.onSetRepeatMode(msg.arg1);
                         break;
-                    case MSG_SET_SHUFFLE_MODE_ENABLED:
-                        cb.onSetShuffleModeEnabled((boolean) msg.obj);
-                        break;
                     case MSG_SET_SHUFFLE_MODE:
                         cb.onSetShuffleMode(msg.arg1);
                         break;
@@ -3206,7 +3136,6 @@
         @RatingCompat.Style int mRatingType;
         boolean mCaptioningEnabled;
         @PlaybackStateCompat.RepeatMode int mRepeatMode;
-        boolean mShuffleModeEnabled;
         @PlaybackStateCompat.ShuffleMode int mShuffleMode;
 
         public MediaSessionImplApi21(Context context, String tag) {
@@ -3381,22 +3310,6 @@
         }
 
         @Override
-        public void setShuffleModeEnabled(boolean enabled) {
-            if (mShuffleModeEnabled != enabled) {
-                mShuffleModeEnabled = enabled;
-                int size = mExtraControllerCallbacks.beginBroadcast();
-                for (int i = size - 1; i >= 0; i--) {
-                    IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
-                    try {
-                        cb.onShuffleModeChangedDeprecated(enabled);
-                    } catch (RemoteException e) {
-                    }
-                }
-                mExtraControllerCallbacks.finishBroadcast();
-            }
-        }
-
-        @Override
         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
             if (mShuffleMode != shuffleMode) {
                 mShuffleMode = shuffleMode;
@@ -3625,9 +3538,8 @@
             }
 
             @Override
-            public void setShuffleModeEnabledDeprecated(boolean enabled) throws RemoteException {
-                // Will not be called.
-                throw new AssertionError();
+            public void setShuffleModeEnabledRemoved(boolean enabled) throws RemoteException {
+                // Do nothing.
             }
 
             @Override
@@ -3713,8 +3625,8 @@
             }
 
             @Override
-            public boolean isShuffleModeEnabledDeprecated() {
-                return mShuffleModeEnabled;
+            public boolean isShuffleModeEnabledRemoved() {
+                return false;
             }
 
             @Override
diff --git a/android/support/v4/media/session/PlaybackStateCompat.java b/android/support/v4/media/session/PlaybackStateCompat.java
index eee09c5..d7634b0 100644
--- a/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/android/support/v4/media/session/PlaybackStateCompat.java
@@ -202,6 +202,7 @@
      * @see Builder#setActions(long)
      * @deprecated Use {@link #ACTION_SET_SHUFFLE_MODE} instead.
      */
+    @Deprecated
     public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 1 << 19;
 
     /**
diff --git a/android/support/v4/net/ConnectivityManagerCompat.java b/android/support/v4/net/ConnectivityManagerCompat.java
index cdddb68..c08cac8 100644
--- a/android/support/v4/net/ConnectivityManagerCompat.java
+++ b/android/support/v4/net/ConnectivityManagerCompat.java
@@ -32,6 +32,8 @@
 import android.net.NetworkInfo;
 import android.os.Build;
 import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresPermission;
 import android.support.annotation.RestrictTo;
 
@@ -91,7 +93,7 @@
      */
     @SuppressWarnings("deprecation")
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
-    public static boolean isActiveNetworkMetered(ConnectivityManager cm) {
+    public static boolean isActiveNetworkMetered(@NonNull ConnectivityManager cm) {
         if (Build.VERSION.SDK_INT >= 16) {
             return cm.isActiveNetworkMetered();
         } else {
@@ -128,8 +130,10 @@
      * potentially-stale value from
      * {@link ConnectivityManager#EXTRA_NETWORK_INFO}. May be {@code null}.
      */
+    @Nullable
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
-    public static NetworkInfo getNetworkInfoFromBroadcast(ConnectivityManager cm, Intent intent) {
+    public static NetworkInfo getNetworkInfoFromBroadcast(@NonNull ConnectivityManager cm,
+            @NonNull Intent intent) {
         final NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
         if (info != null) {
             return cm.getNetworkInfo(info.getType());
@@ -147,7 +151,7 @@
      *         or {@link #RESTRICT_BACKGROUND_STATUS_WHITELISTED}
      */
     @RestrictBackgroundStatus
-    public static int getRestrictBackgroundStatus(ConnectivityManager cm) {
+    public static int getRestrictBackgroundStatus(@NonNull ConnectivityManager cm) {
         if (Build.VERSION.SDK_INT >= 24) {
             return cm.getRestrictBackgroundStatus();
         } else {
diff --git a/android/support/v4/net/TrafficStatsCompat.java b/android/support/v4/net/TrafficStatsCompat.java
index 1049fa5..b74b8d0 100644
--- a/android/support/v4/net/TrafficStatsCompat.java
+++ b/android/support/v4/net/TrafficStatsCompat.java
@@ -19,6 +19,7 @@
 import android.net.TrafficStats;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
+import android.support.annotation.NonNull;
 
 import java.net.DatagramSocket;
 import java.net.Socket;
@@ -131,7 +132,7 @@
      *
      * @see #setThreadStatsTag(int)
      */
-    public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+    public static void tagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
         if (Build.VERSION.SDK_INT >= 24) {
             TrafficStats.tagDatagramSocket(socket);
         } else {
@@ -148,7 +149,7 @@
     /**
      * Remove any statistics parameters from the given {@link DatagramSocket}.
      */
-    public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+    public static void untagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
         if (Build.VERSION.SDK_INT >= 24) {
             TrafficStats.untagDatagramSocket(socket);
         } else {
diff --git a/android/support/v4/provider/FontRequest.java b/android/support/v4/provider/FontRequest.java
index cb32f06..b14f85e 100644
--- a/android/support/v4/provider/FontRequest.java
+++ b/android/support/v4/provider/FontRequest.java
@@ -89,6 +89,7 @@
      * Returns the selected font provider's authority. This tells the system what font provider
      * it should request the font from.
      */
+    @NonNull
     public String getProviderAuthority() {
         return mProviderAuthority;
     }
@@ -97,6 +98,7 @@
      * Returns the selected font provider's package. This helps the system verify that the provider
      * identified by the given authority is the one requested.
      */
+    @NonNull
     public String getProviderPackage() {
         return mProviderPackage;
     }
@@ -105,6 +107,7 @@
      * Returns the query string. Refer to your font provider's documentation on the format of this
      * string.
      */
+    @NonNull
     public String getQuery() {
         return mQuery;
     }
diff --git a/android/support/v4/provider/FontsContractCompat.java b/android/support/v4/provider/FontsContractCompat.java
index 6ad46a1..9ef1b0b 100644
--- a/android/support/v4/provider/FontsContractCompat.java
+++ b/android/support/v4/provider/FontsContractCompat.java
@@ -17,7 +17,6 @@
 package android.support.v4.provider;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.v4.content.res.FontResourcesParserCompat.FetchStrategy;
 
 import android.annotation.SuppressLint;
 import android.content.ContentResolver;
@@ -46,17 +45,16 @@
 import android.support.annotation.RestrictTo;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.res.FontResourcesParserCompat;
+import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.graphics.TypefaceCompat;
 import android.support.v4.graphics.TypefaceCompatUtil;
 import android.support.v4.provider.SelfDestructiveThread.ReplyCallback;
 import android.support.v4.util.LruCache;
 import android.support.v4.util.Preconditions;
 import android.support.v4.util.SimpleArrayMap;
-import android.widget.TextView;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -166,11 +164,11 @@
     // space open for new provider codes, these should all be negative numbers.
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
-    public static final int RESULT_CODE_PROVIDER_NOT_FOUND = -1;
+    /* package */ static final int RESULT_CODE_PROVIDER_NOT_FOUND = -1;
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
-    public static final int RESULT_CODE_WRONG_CERTIFICATES = -2;
-    // Note -3 is used by Typeface to indicate the font failed to load.
+    /* package */ static final int RESULT_CODE_WRONG_CERTIFICATES = -2;
+    // Note -3 is used by FontRequestCallback to indicate the font failed to load.
 
     private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
 
@@ -179,51 +177,87 @@
             new SelfDestructiveThread("fonts", Process.THREAD_PRIORITY_BACKGROUND,
                     BACKGROUND_THREAD_KEEP_ALIVE_DURATION_MS);
 
-    private static Typeface getFontInternal(final Context context, final FontRequest request,
+    @NonNull
+    private static TypefaceResult getFontInternal(final Context context, final FontRequest request,
             int style) {
         FontFamilyResult result;
         try {
             result = fetchFonts(context, null /* CancellationSignal */, request);
         } catch (PackageManager.NameNotFoundException e) {
-            return null;
+            return new TypefaceResult(null, FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
         }
         if (result.getStatusCode() == FontFamilyResult.STATUS_OK) {
-            return TypefaceCompat.createFromFontInfo(context, null /* CancellationSignal */,
-                    result.getFonts(), style);
+            final Typeface typeface = TypefaceCompat.createFromFontInfo(
+                    context, null /* CancellationSignal */, result.getFonts(), style);
+            return new TypefaceResult(typeface, typeface != null
+                    ? FontRequestCallback.RESULT_OK
+                    : FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
         }
-        return null;
+        int resultCode = result.getStatusCode() == FontFamilyResult.STATUS_WRONG_CERTIFICATES
+                ? FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES
+                : FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR;
+        return new TypefaceResult(null, resultCode);
     }
 
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
-    private static final SimpleArrayMap<String, ArrayList<ReplyCallback<Typeface>>>
+    private static final SimpleArrayMap<String, ArrayList<ReplyCallback<TypefaceResult>>>
             sPendingReplies = new SimpleArrayMap<>();
 
+    private static final class TypefaceResult {
+        final Typeface mTypeface;
+        @FontRequestCallback.FontRequestFailReason final int mResult;
+
+        TypefaceResult(@Nullable Typeface typeface,
+                @FontRequestCallback.FontRequestFailReason int result) {
+            mTypeface = typeface;
+            mResult = result;
+        }
+    }
+
+    /**
+     * Used for tests, should not be used otherwise.
+     * @hide
+     **/
+    @RestrictTo(LIBRARY_GROUP)
+    public static final void resetCache() {
+        sTypefaceCache.evictAll();
+    }
+
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
     public static Typeface getFontSync(final Context context, final FontRequest request,
-            final @Nullable TextView targetView, @FetchStrategy int strategy, int timeout,
+            final @Nullable ResourcesCompat.FontCallback fontCallback,
+            final @Nullable Handler handler, boolean isBlockingFetch, int timeout,
             final int style) {
         final String id = request.getIdentifier() + "-" + style;
         Typeface cached = sTypefaceCache.get(id);
         if (cached != null) {
+            if (fontCallback != null) {
+                fontCallback.onFontRetrieved(cached);
+            }
             return cached;
         }
 
-        final boolean isBlockingFetch =
-                strategy == FontResourcesParserCompat.FETCH_STRATEGY_BLOCKING;
-
         if (isBlockingFetch && timeout == FontResourcesParserCompat.INFINITE_TIMEOUT_VALUE) {
             // Wait forever. No need to post to the thread.
-            return getFontInternal(context, request, style);
+            TypefaceResult typefaceResult = getFontInternal(context, request, style);
+            if (fontCallback != null) {
+                if (typefaceResult.mResult == FontFamilyResult.STATUS_OK) {
+                    fontCallback.callbackSuccessAsync(typefaceResult.mTypeface, handler);
+                } else {
+                    fontCallback.callbackFailAsync(typefaceResult.mResult, handler);
+                }
+            }
+            return typefaceResult.mTypeface;
         }
 
-        final Callable<Typeface> fetcher = new Callable<Typeface>() {
+        final Callable<TypefaceResult> fetcher = new Callable<TypefaceResult>() {
             @Override
-            public Typeface call() throws Exception {
-                Typeface typeface = getFontInternal(context, request, style);
-                if (typeface != null) {
-                    sTypefaceCache.put(id, typeface);
+            public TypefaceResult call() throws Exception {
+                TypefaceResult typeface = getFontInternal(context, request, style);
+                if (typeface.mTypeface != null) {
+                    sTypefaceCache.put(id, typeface.mTypeface);
                 }
                 return typeface;
             }
@@ -231,37 +265,42 @@
 
         if (isBlockingFetch) {
             try {
-                return sBackgroundThread.postAndWait(fetcher, timeout);
+                return sBackgroundThread.postAndWait(fetcher, timeout).mTypeface;
             } catch (InterruptedException e) {
                 return null;
             }
         } else {
-            final WeakReference<TextView> textViewWeak = new WeakReference<TextView>(targetView);
-            final ReplyCallback<Typeface> reply = new ReplyCallback<Typeface>() {
-                @Override
-                public void onReply(final Typeface typeface) {
-                    final TextView textView = textViewWeak.get();
-                    if (textView != null) {
-                        targetView.setTypeface(typeface, style);
-                    }
-                }
-            };
+            final ReplyCallback<TypefaceResult> reply = fontCallback == null ? null
+                    : new ReplyCallback<TypefaceResult>() {
+                        @Override
+                        public void onReply(final TypefaceResult typeface) {
+                            if (typeface.mResult == FontFamilyResult.STATUS_OK) {
+                                fontCallback.callbackSuccessAsync(typeface.mTypeface, handler);
+                            } else {
+                                fontCallback.callbackFailAsync(typeface.mResult, handler);
+                            }
+                        }
+                    };
 
             synchronized (sLock) {
                 if (sPendingReplies.containsKey(id)) {
                     // Already requested. Do not request the same provider again and insert the
                     // reply to the queue instead.
-                    sPendingReplies.get(id).add(reply);
+                    if (reply != null) {
+                        sPendingReplies.get(id).add(reply);
+                    }
                     return null;
                 }
-                ArrayList<ReplyCallback<Typeface>> pendingReplies = new ArrayList<>();
-                pendingReplies.add(reply);
-                sPendingReplies.put(id, pendingReplies);
+                if (reply != null) {
+                    ArrayList<ReplyCallback<TypefaceResult>> pendingReplies = new ArrayList<>();
+                    pendingReplies.add(reply);
+                    sPendingReplies.put(id, pendingReplies);
+                }
             }
-            sBackgroundThread.postAndReply(fetcher, new ReplyCallback<Typeface>() {
+            sBackgroundThread.postAndReply(fetcher, new ReplyCallback<TypefaceResult>() {
                 @Override
-                public void onReply(final Typeface typeface) {
-                    final ArrayList<ReplyCallback<Typeface>> replies;
+                public void onReply(final TypefaceResult typeface) {
+                    final ArrayList<ReplyCallback<TypefaceResult>> replies;
                     synchronized (sLock) {
                         replies = sPendingReplies.get(id);
                         sPendingReplies.remove(id);
@@ -269,7 +308,7 @@
                     for (int i = 0; i < replies.size(); ++i) {
                         replies.get(i).onReply(typeface);
                     }
-                };
+                }
             });
             return null;
         }
@@ -292,8 +331,9 @@
          * @param weight An integer that indicates the font weight.
          * @param italic A boolean that indicates the font is italic style or not.
          * @param resultCode A boolean that indicates the font contents is ready.
+         *
+         * @hide
          */
-        /** @hide */
         @RestrictTo(LIBRARY_GROUP)
         public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex,
                 @IntRange(from = 1, to = 1000) int weight,
@@ -396,6 +436,9 @@
      * Interface used to receive asynchronously fetched typefaces.
      */
     public static class FontRequestCallback {
+        /** @hide */
+        @RestrictTo(LIBRARY_GROUP)
+        public static final int RESULT_OK = Columns.RESULT_CODE_OK;
         /**
          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
          * provider was not found on the device.
@@ -412,6 +455,11 @@
          */
         public static final int FAIL_REASON_FONT_LOAD_ERROR = -3;
         /**
+         * Constant that signals that the font was not loaded due to security issues. This usually
+         * means the font was attempted to load on a restricted context.
+         */
+        public static final int FAIL_REASON_SECURITY_VIOLATION = -4;
+        /**
          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
          * provider did not return any results for the given query.
          */
@@ -431,9 +479,10 @@
         @RestrictTo(LIBRARY_GROUP)
         @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
                 FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE,
-                FAIL_REASON_MALFORMED_QUERY, FAIL_REASON_WRONG_CERTIFICATES })
+                FAIL_REASON_MALFORMED_QUERY, FAIL_REASON_WRONG_CERTIFICATES,
+                FAIL_REASON_SECURITY_VIOLATION, RESULT_OK })
         @Retention(RetentionPolicy.SOURCE)
-        @interface FontRequestFailReason {}
+        public @interface FontRequestFailReason {}
 
         public FontRequestCallback() {}
 
@@ -600,6 +649,7 @@
      * @param fonts An array of {@link FontInfo} to be used to create a Typeface.
      * @return A Typeface object. Returns null if typeface creation fails.
      */
+    @Nullable
     public static Typeface buildTypeface(@NonNull Context context,
             @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) {
         return TypefaceCompat.createFromFontInfo(context, cancellationSignal, fonts,
diff --git a/android/support/v4/provider/SelfDestructiveThread.java b/android/support/v4/provider/SelfDestructiveThread.java
index 885799b..7cfe1f8 100644
--- a/android/support/v4/provider/SelfDestructiveThread.java
+++ b/android/support/v4/provider/SelfDestructiveThread.java
@@ -129,7 +129,7 @@
 
     /**
      * Execute the specific callable object on this thread and call the reply callback on the
-     * calling thread once it finishs.
+     * calling thread once it finishes.
      */
     public <T> void postAndReply(final Callable<T> callable, final ReplyCallback<T> reply) {
         final Handler callingHandler = new Handler();
diff --git a/android/support/v4/util/AtomicFile.java b/android/support/v4/util/AtomicFile.java
index 275f4e2..aefe705 100644
--- a/android/support/v4/util/AtomicFile.java
+++ b/android/support/v4/util/AtomicFile.java
@@ -16,6 +16,8 @@
 
 package android.support.v4.util;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 
 import java.io.File;
@@ -48,7 +50,7 @@
      * Create a new AtomicFile for a file located at the given File path.
      * The secondary backup file will be the same file path with ".bak" appended.
      */
-    public AtomicFile(File baseName) {
+    public AtomicFile(@NonNull File baseName) {
         mBaseName = baseName;
         mBackupName = new File(baseName.getPath() + ".bak");
     }
@@ -57,6 +59,7 @@
      * Return the path to the base file.  You should not generally use this,
      * as the data at that path may not be valid.
      */
+    @NonNull
     public File getBaseFile() {
         return mBaseName;
     }
@@ -83,6 +86,7 @@
      * safe (or will be lost).  You must do your own threading protection for
      * access to AtomicFile.
      */
+    @NonNull
     public FileOutputStream startWrite() throws IOException {
         // Rename the current file so it may be used as a backup during the next read
         if (mBaseName.exists()) {
@@ -95,7 +99,7 @@
                 mBaseName.delete();
             }
         }
-        FileOutputStream str = null;
+        FileOutputStream str;
         try {
             str = new FileOutputStream(mBaseName);
         } catch (FileNotFoundException e) {
@@ -118,7 +122,7 @@
      * commit the new data.  The next attempt to read the atomic file
      * will return the new file stream.
      */
-    public void finishWrite(FileOutputStream str) {
+    public void finishWrite(@Nullable FileOutputStream str) {
         if (str != null) {
             sync(str);
             try {
@@ -135,7 +139,7 @@
      * returned by {@link #startWrite()}.  This will close the current
      * write stream, and roll back to the previous state of the file.
      */
-    public void failWrite(FileOutputStream str) {
+    public void failWrite(@Nullable FileOutputStream str) {
         if (str != null) {
             sync(str);
             try {
@@ -160,6 +164,7 @@
      * be dropped.  You must do your own threading protection for access to
      * AtomicFile.
      */
+    @NonNull
     public FileInputStream openRead() throws FileNotFoundException {
         if (mBackupName.exists()) {
             mBaseName.delete();
@@ -172,6 +177,7 @@
      * A convenience for {@link #openRead()} that also reads all of the
      * file contents into a byte array which is returned.
      */
+    @NonNull
     public byte[] readFully() throws IOException {
         FileInputStream stream = openRead();
         try {
@@ -200,11 +206,9 @@
         }
     }
 
-    static boolean sync(FileOutputStream stream) {
+    private static boolean sync(@NonNull FileOutputStream stream) {
         try {
-            if (stream != null) {
-                stream.getFD().sync();
-            }
+            stream.getFD().sync();
             return true;
         } catch (IOException e) {
         }
diff --git a/android/support/v4/util/ObjectsCompat.java b/android/support/v4/util/ObjectsCompat.java
index 4650060..b6c740e 100644
--- a/android/support/v4/util/ObjectsCompat.java
+++ b/android/support/v4/util/ObjectsCompat.java
@@ -16,6 +16,7 @@
 package android.support.v4.util;
 
 import android.os.Build;
+import android.support.annotation.Nullable;
 
 import java.util.Objects;
 
@@ -43,7 +44,7 @@
      *         and {@code false} otherwise
      * @see Object#equals(Object)
      */
-    public static boolean equals(Object a, Object b) {
+    public static boolean equals(@Nullable Object a, @Nullable Object b) {
         if (Build.VERSION.SDK_INT >= 19) {
             return Objects.equals(a, b);
         } else {
diff --git a/android/support/v4/util/Pair.java b/android/support/v4/util/Pair.java
index 46ea5cd..9047aec 100644
--- a/android/support/v4/util/Pair.java
+++ b/android/support/v4/util/Pair.java
@@ -16,14 +16,17 @@
 
 package android.support.v4.util;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
 /**
  * Container to ease passing around a tuple of two objects. This object provides a sensible
  * implementation of equals(), returning true if equals() is true on each of the contained
  * objects.
  */
 public class Pair<F, S> {
-    public final F first;
-    public final S second;
+    public final @Nullable F first;
+    public final @Nullable S second;
 
     /**
      * Constructor for a Pair.
@@ -31,7 +34,7 @@
      * @param first the first object in the Pair
      * @param second the second object in the pair
      */
-    public Pair(F first, S second) {
+    public Pair(@Nullable F first, @Nullable S second) {
         this.first = first;
         this.second = second;
     }
@@ -78,7 +81,8 @@
      * @param b the second object in the pair
      * @return a Pair that is templatized with the types of a and b
      */
-    public static <A, B> Pair <A, B> create(A a, B b) {
+    @NonNull
+    public static <A, B> Pair <A, B> create(@Nullable A a, @Nullable B b) {
         return new Pair<A, B>(a, b);
     }
 }
diff --git a/android/support/v4/util/Pools.java b/android/support/v4/util/Pools.java
index 6882660..99fd888 100644
--- a/android/support/v4/util/Pools.java
+++ b/android/support/v4/util/Pools.java
@@ -18,6 +18,9 @@
 package android.support.v4.util;
 
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
 /**
  * Helper class for creating pools of objects. An example use looks like this:
  * <pre>
@@ -53,6 +56,7 @@
         /**
          * @return An instance from the pool if such, null otherwise.
          */
+        @Nullable
         public T acquire();
 
         /**
@@ -63,7 +67,7 @@
          *
          * @throws IllegalStateException If the instance is already in the pool.
          */
-        public boolean release(T instance);
+        public boolean release(@NonNull T instance);
     }
 
     private Pools() {
@@ -108,7 +112,7 @@
         }
 
         @Override
-        public boolean release(T instance) {
+        public boolean release(@NonNull T instance) {
             if (isInPool(instance)) {
                 throw new IllegalStateException("Already in the pool!");
             }
@@ -120,7 +124,7 @@
             return false;
         }
 
-        private boolean isInPool(T instance) {
+        private boolean isInPool(@NonNull T instance) {
             for (int i = 0; i < mPoolSize; i++) {
                 if (mPool[i] == instance) {
                     return true;
@@ -157,7 +161,7 @@
         }
 
         @Override
-        public boolean release(T element) {
+        public boolean release(@NonNull T element) {
             synchronized (mLock) {
                 return super.release(element);
             }
diff --git a/android/support/v4/view/AbsSavedState.java b/android/support/v4/view/AbsSavedState.java
index 4cf38ac..86f491f 100644
--- a/android/support/v4/view/AbsSavedState.java
+++ b/android/support/v4/view/AbsSavedState.java
@@ -18,6 +18,8 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 /**
  * A {@link Parcelable} implementation that should be used by inheritance
@@ -40,7 +42,7 @@
      *
      * @param superState The state of the superclass of this view
      */
-    protected AbsSavedState(Parcelable superState) {
+    protected AbsSavedState(@NonNull Parcelable superState) {
         if (superState == null) {
             throw new IllegalArgumentException("superState must not be null");
         }
@@ -52,7 +54,7 @@
      *
      * @param source parcel to read from
      */
-    protected AbsSavedState(Parcel source) {
+    protected AbsSavedState(@NonNull Parcel source) {
         this(source, null);
     }
 
@@ -62,11 +64,12 @@
      * @param source parcel to read from
      * @param loader ClassLoader to use for reading
      */
-    protected AbsSavedState(Parcel source, ClassLoader loader) {
+    protected AbsSavedState(@NonNull Parcel source, @Nullable ClassLoader loader) {
         Parcelable superState = source.readParcelable(loader);
         mSuperState = superState != null ? superState : EMPTY_STATE;
     }
 
+    @Nullable
     public final Parcelable getSuperState() {
         return mSuperState;
     }
diff --git a/android/support/v4/view/AsyncLayoutInflater.java b/android/support/v4/view/AsyncLayoutInflater.java
index e194a50..d26e5b8 100644
--- a/android/support/v4/view/AsyncLayoutInflater.java
+++ b/android/support/v4/view/AsyncLayoutInflater.java
@@ -107,7 +107,8 @@
     };
 
     public interface OnInflateFinishedListener {
-        void onInflateFinished(View view, int resid, ViewGroup parent);
+        void onInflateFinished(@NonNull View view, @LayoutRes int resid,
+                @Nullable ViewGroup parent);
     }
 
     private static class InflateRequest {
diff --git a/android/support/v4/view/PagerAdapter.java b/android/support/v4/view/PagerAdapter.java
index 1b1dc3d..a8fb099 100644
--- a/android/support/v4/view/PagerAdapter.java
+++ b/android/support/v4/view/PagerAdapter.java
@@ -19,6 +19,8 @@
 import android.database.DataSetObservable;
 import android.database.DataSetObserver;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -92,7 +94,7 @@
      * @param container The containing View which is displaying this adapter's
      * page views.
      */
-    public void startUpdate(ViewGroup container) {
+    public void startUpdate(@NonNull ViewGroup container) {
         startUpdate((View) container);
     }
 
@@ -107,7 +109,8 @@
      * @return Returns an Object representing the new page.  This does not
      * need to be a View, but can be some other container of the page.
      */
-    public Object instantiateItem(ViewGroup container, int position) {
+    @NonNull
+    public Object instantiateItem(@NonNull ViewGroup container, int position) {
         return instantiateItem((View) container, position);
     }
 
@@ -121,7 +124,7 @@
      * @param object The same object that was returned by
      * {@link #instantiateItem(View, int)}.
      */
-    public void destroyItem(ViewGroup container, int position, Object object) {
+    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
         destroyItem((View) container, position, object);
     }
 
@@ -134,7 +137,7 @@
      * @param object The same object that was returned by
      * {@link #instantiateItem(View, int)}.
      */
-    public void setPrimaryItem(ViewGroup container, int position, Object object) {
+    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
         setPrimaryItem((View) container, position, object);
     }
 
@@ -145,7 +148,7 @@
      * @param container The containing View which is displaying this adapter's
      * page views.
      */
-    public void finishUpdate(ViewGroup container) {
+    public void finishUpdate(@NonNull ViewGroup container) {
         finishUpdate((View) container);
     }
 
@@ -157,7 +160,7 @@
      * @deprecated Use {@link #startUpdate(ViewGroup)}
      */
     @Deprecated
-    public void startUpdate(View container) {
+    public void startUpdate(@NonNull View container) {
     }
 
     /**
@@ -174,7 +177,8 @@
      * @deprecated Use {@link #instantiateItem(ViewGroup, int)}
      */
     @Deprecated
-    public Object instantiateItem(View container, int position) {
+    @NonNull
+    public Object instantiateItem(@NonNull View container, int position) {
         throw new UnsupportedOperationException(
                 "Required method instantiateItem was not overridden");
     }
@@ -192,7 +196,7 @@
      * @deprecated Use {@link #destroyItem(ViewGroup, int, Object)}
      */
     @Deprecated
-    public void destroyItem(View container, int position, Object object) {
+    public void destroyItem(@NonNull View container, int position, @NonNull Object object) {
         throw new UnsupportedOperationException("Required method destroyItem was not overridden");
     }
 
@@ -208,7 +212,7 @@
      * @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)}
      */
     @Deprecated
-    public void setPrimaryItem(View container, int position, Object object) {
+    public void setPrimaryItem(@NonNull View container, int position, @NonNull Object object) {
     }
 
     /**
@@ -221,7 +225,7 @@
      * @deprecated Use {@link #finishUpdate(ViewGroup)}
      */
     @Deprecated
-    public void finishUpdate(View container) {
+    public void finishUpdate(@NonNull View container) {
     }
 
     /**
@@ -233,7 +237,7 @@
      * @param object Object to check for association with <code>view</code>
      * @return true if <code>view</code> is associated with the key object <code>object</code>
      */
-    public abstract boolean isViewFromObject(View view, Object object);
+    public abstract boolean isViewFromObject(@NonNull View view, @NonNull Object object);
 
     /**
      * Save any instance state associated with this adapter and its pages that should be
@@ -241,6 +245,7 @@
      *
      * @return Saved state for this adapter
      */
+    @Nullable
     public Parcelable saveState() {
         return null;
     }
@@ -252,7 +257,7 @@
      * @param state State previously saved by a call to {@link #saveState()}
      * @param loader A ClassLoader that should be used to instantiate any restored objects
      */
-    public void restoreState(Parcelable state, ClassLoader loader) {
+    public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
     }
 
     /**
@@ -270,7 +275,7 @@
      *         {@link #POSITION_UNCHANGED} if the object's position has not changed,
      *         or {@link #POSITION_NONE} if the item is no longer present.
      */
-    public int getItemPosition(Object object) {
+    public int getItemPosition(@NonNull Object object) {
         return POSITION_UNCHANGED;
     }
 
@@ -292,7 +297,7 @@
      *
      * @param observer The {@link android.database.DataSetObserver} which will receive callbacks.
      */
-    public void registerDataSetObserver(DataSetObserver observer) {
+    public void registerDataSetObserver(@NonNull DataSetObserver observer) {
         mObservable.registerObserver(observer);
     }
 
@@ -301,7 +306,7 @@
      *
      * @param observer The {@link android.database.DataSetObserver} which will be unregistered.
      */
-    public void unregisterDataSetObserver(DataSetObserver observer) {
+    public void unregisterDataSetObserver(@NonNull DataSetObserver observer) {
         mObservable.unregisterObserver(observer);
     }
 
@@ -320,6 +325,7 @@
      * @param position The position of the title requested
      * @return A title for the requested page
      */
+    @Nullable
     public CharSequence getPageTitle(int position) {
         return null;
     }
diff --git a/android/support/v4/view/PagerTabStrip.java b/android/support/v4/view/PagerTabStrip.java
index f4c0b21..6c88572 100644
--- a/android/support/v4/view/PagerTabStrip.java
+++ b/android/support/v4/view/PagerTabStrip.java
@@ -24,6 +24,8 @@
 import android.support.annotation.ColorInt;
 import android.support.annotation.ColorRes;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.content.ContextCompat;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -76,11 +78,11 @@
     private float mInitialMotionY;
     private int mTouchSlop;
 
-    public PagerTabStrip(Context context) {
+    public PagerTabStrip(@NonNull Context context) {
         this(context, null);
     }
 
-    public PagerTabStrip(Context context, AttributeSet attrs) {
+    public PagerTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
 
         mIndicatorColor = mTextColor;
diff --git a/android/support/v4/view/PagerTitleStrip.java b/android/support/v4/view/PagerTitleStrip.java
index b63e4f4..79a6240 100644
--- a/android/support/v4/view/PagerTitleStrip.java
+++ b/android/support/v4/view/PagerTitleStrip.java
@@ -22,6 +22,8 @@
 import android.graphics.drawable.Drawable;
 import android.support.annotation.ColorInt;
 import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.widget.TextViewCompat;
 import android.text.TextUtils.TruncateAt;
 import android.text.method.SingleLineTransformationMethod;
@@ -102,11 +104,11 @@
         text.setTransformationMethod(new SingleLineAllCapsTransform(text.getContext()));
     }
 
-    public PagerTitleStrip(Context context) {
+    public PagerTitleStrip(@NonNull Context context) {
         this(context, null);
     }
 
-    public PagerTitleStrip(Context context, AttributeSet attrs) {
+    public PagerTitleStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
 
         addView(mPrevText = new TextView(context));
diff --git a/android/support/v4/view/ViewCompat.java b/android/support/v4/view/ViewCompat.java
index e7443d7..34a198a 100644
--- a/android/support/v4/view/ViewCompat.java
+++ b/android/support/v4/view/ViewCompat.java
@@ -3029,7 +3029,10 @@
      *        {@link ViewGroup#getChildDrawingOrder(int, int)}, false otherwise
      *
      * <p>Prior to API 7 this will have no effect.</p>
+     *
+     * @deprecated Use {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)} directly.
      */
+    @Deprecated
     public static void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
        IMPL.setChildrenDrawingOrderEnabled(viewGroup, enabled);
     }
diff --git a/android/support/v4/view/ViewPager.java b/android/support/v4/view/ViewPager.java
index 8cd973c..36d8696 100644
--- a/android/support/v4/view/ViewPager.java
+++ b/android/support/v4/view/ViewPager.java
@@ -347,7 +347,7 @@
          *                 position of the pager. 0 is front and center. 1 is one full
          *                 page position to the right, and -1 is one page position to the left.
          */
-        void transformPage(View page, float position);
+        void transformPage(@NonNull View page, float position);
     }
 
     /**
@@ -381,12 +381,12 @@
     public @interface DecorView {
     }
 
-    public ViewPager(Context context) {
+    public ViewPager(@NonNull Context context) {
         super(context);
         initViewPager();
     }
 
-    public ViewPager(Context context, AttributeSet attrs) {
+    public ViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         initViewPager();
     }
@@ -496,7 +496,7 @@
      *
      * @param adapter Adapter to use
      */
-    public void setAdapter(PagerAdapter adapter) {
+    public void setAdapter(@Nullable PagerAdapter adapter) {
         if (mAdapter != null) {
             mAdapter.setViewPagerObserver(null);
             mAdapter.startUpdate(this);
@@ -561,6 +561,7 @@
      *
      * @return The currently registered PagerAdapter
      */
+    @Nullable
     public PagerAdapter getAdapter() {
         return mAdapter;
     }
@@ -712,7 +713,7 @@
      *
      * @param listener listener to add
      */
-    public void addOnPageChangeListener(OnPageChangeListener listener) {
+    public void addOnPageChangeListener(@NonNull OnPageChangeListener listener) {
         if (mOnPageChangeListeners == null) {
             mOnPageChangeListeners = new ArrayList<>();
         }
@@ -725,7 +726,7 @@
      *
      * @param listener listener to remove
      */
-    public void removeOnPageChangeListener(OnPageChangeListener listener) {
+    public void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) {
         if (mOnPageChangeListeners != null) {
             mOnPageChangeListeners.remove(listener);
         }
@@ -757,7 +758,8 @@
      *                            to be drawn from last to first instead of first to last.
      * @param transformer PageTransformer that will modify each page's animation properties
      */
-    public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
+    public void setPageTransformer(boolean reverseDrawingOrder,
+            @Nullable PageTransformer transformer) {
         setPageTransformer(reverseDrawingOrder, transformer, View.LAYER_TYPE_HARDWARE);
     }
 
@@ -774,8 +776,8 @@
      *                      {@link View#LAYER_TYPE_SOFTWARE}, or
      *                      {@link View#LAYER_TYPE_NONE}.
      */
-    public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer,
-            int pageLayerType) {
+    public void setPageTransformer(boolean reverseDrawingOrder,
+            @Nullable PageTransformer transformer, int pageLayerType) {
         final boolean hasTransformer = transformer != null;
         final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
         mPageTransformer = transformer;
@@ -881,7 +883,7 @@
      *
      * @param d Drawable to display between pages
      */
-    public void setPageMarginDrawable(Drawable d) {
+    public void setPageMarginDrawable(@Nullable Drawable d) {
         mMarginDrawable = d;
         if (d != null) refreshDrawableState();
         setWillNotDraw(d == null);
@@ -1383,7 +1385,7 @@
         Parcelable adapterState;
         ClassLoader loader;
 
-        public SavedState(Parcelable superState) {
+        public SavedState(@NonNull Parcelable superState) {
             super(superState);
         }
 
@@ -2744,7 +2746,7 @@
      * @param event The key event to execute.
      * @return Return true if the event was handled, else false.
      */
-    public boolean executeKeyEvent(KeyEvent event) {
+    public boolean executeKeyEvent(@NonNull KeyEvent event) {
         boolean handled = false;
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             switch (event.getKeyCode()) {
diff --git a/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index f9329fa..327be01 100644
--- a/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -1863,7 +1863,7 @@
     }
 
     /**
-     * Sets whether this node is visible to the user.
+     * Gets whether this node is visible to the user.
      *
      * @return Whether the node is visible to the user.
      */
diff --git a/android/support/v4/widget/AutoScrollHelper.java b/android/support/v4/widget/AutoScrollHelper.java
index d0407be..60d208d 100644
--- a/android/support/v4/widget/AutoScrollHelper.java
+++ b/android/support/v4/widget/AutoScrollHelper.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources;
 import android.os.SystemClock;
+import android.support.annotation.NonNull;
 import android.support.v4.view.ViewCompat;
 import android.util.DisplayMetrics;
 import android.view.MotionEvent;
@@ -205,7 +206,7 @@
      *
      * @param target The view to automatically scroll.
      */
-    public AutoScrollHelper(View target) {
+    public AutoScrollHelper(@NonNull View target) {
         mTarget = target;
 
         final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
@@ -289,6 +290,7 @@
      *            {@link #NO_MAX} to leave the relative value unconstrained.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setMaximumVelocity(float horizontalMax, float verticalMax) {
         mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000f;
         mMaximumVelocity[VERTICAL] = verticalMax / 1000f;
@@ -307,6 +309,7 @@
      *            {@link #NO_MIN} to leave the relative value unconstrained.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setMinimumVelocity(float horizontalMin, float verticalMin) {
         mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000f;
         mMinimumVelocity[VERTICAL] = verticalMin / 1000f;
@@ -328,6 +331,7 @@
      *            ignore.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setRelativeVelocity(float horizontal, float vertical) {
         mRelativeVelocity[HORIZONTAL] = horizontal / 1000f;
         mRelativeVelocity[VERTICAL] = vertical / 1000f;
@@ -349,6 +353,7 @@
      * @param type The type of edge to use.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setEdgeType(int type) {
         mEdgeType = type;
         return this;
@@ -368,6 +373,7 @@
      *            maximum value.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setRelativeEdges(float horizontal, float vertical) {
         mRelativeEdges[HORIZONTAL] = horizontal;
         mRelativeEdges[VERTICAL] = vertical;
@@ -390,6 +396,7 @@
      *            value.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setMaximumEdges(float horizontalMax, float verticalMax) {
         mMaximumEdges[HORIZONTAL] = horizontalMax;
         mMaximumEdges[VERTICAL] = verticalMax;
@@ -407,6 +414,7 @@
      * @param delayMillis The activation delay in milliseconds.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setActivationDelay(int delayMillis) {
         mActivationDelay = delayMillis;
         return this;
@@ -422,6 +430,7 @@
      * @param durationMillis The ramp-up duration in milliseconds.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setRampUpDuration(int durationMillis) {
         mScroller.setRampUpDuration(durationMillis);
         return this;
@@ -437,6 +446,7 @@
      * @param durationMillis The ramp-down duration in milliseconds.
      * @return The scroll helper, which may used to chain setter calls.
      */
+    @NonNull
     public AutoScrollHelper setRampDownDuration(int durationMillis) {
         mScroller.setRampDownDuration(durationMillis);
         return this;
diff --git a/android/support/v4/widget/CircularProgressDrawable.java b/android/support/v4/widget/CircularProgressDrawable.java
index ac29541..2055669 100644
--- a/android/support/v4/widget/CircularProgressDrawable.java
+++ b/android/support/v4/widget/CircularProgressDrawable.java
@@ -132,7 +132,7 @@
     /**
      * @param context application context
      */
-    public CircularProgressDrawable(Context context) {
+    public CircularProgressDrawable(@NonNull Context context) {
         mResources = Preconditions.checkNotNull(context).getResources();
 
         mRing = new Ring();
@@ -215,7 +215,7 @@
      *
      * @param strokeCap stroke cap
      */
-    public void setStrokeCap(Paint.Cap strokeCap) {
+    public void setStrokeCap(@NonNull Paint.Cap strokeCap) {
         mRing.setStrokeCap(strokeCap);
         invalidateSelf();
     }
@@ -225,6 +225,7 @@
      *
      * @return stroke cap
      */
+    @NonNull
     public Paint.Cap getStrokeCap() {
         return mRing.getStrokeCap();
     }
@@ -373,6 +374,7 @@
      *
      * @return list of ARGB colors
      */
+    @NonNull
     public int[] getColorSchemeColors() {
         return mRing.getColors();
     }
@@ -383,7 +385,7 @@
      *
      * @param colors list of ARGB colors to be used in the spinner
      */
-    public void setColorSchemeColors(int... colors) {
+    public void setColorSchemeColors(@NonNull int... colors) {
         mRing.setColors(colors);
         mRing.setColorIndex(0);
         invalidateSelf();
diff --git a/android/support/v4/widget/ContentLoadingProgressBar.java b/android/support/v4/widget/ContentLoadingProgressBar.java
index 98b63a4..356c7b9 100644
--- a/android/support/v4/widget/ContentLoadingProgressBar.java
+++ b/android/support/v4/widget/ContentLoadingProgressBar.java
@@ -17,6 +17,8 @@
 package android.support.v4.widget;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.ProgressBar;
@@ -61,11 +63,11 @@
         }
     };
 
-    public ContentLoadingProgressBar(Context context) {
+    public ContentLoadingProgressBar(@NonNull Context context) {
         this(context, null);
     }
 
-    public ContentLoadingProgressBar(Context context, AttributeSet attrs) {
+    public ContentLoadingProgressBar(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs, 0);
     }
 
diff --git a/android/support/v4/widget/DrawerLayout.java b/android/support/v4/widget/DrawerLayout.java
index c7b40e9..a73e1f1 100644
--- a/android/support/v4/widget/DrawerLayout.java
+++ b/android/support/v4/widget/DrawerLayout.java
@@ -248,7 +248,7 @@
          * @param drawerView The child view that was moved
          * @param slideOffset The new offset of this drawer within its range, from 0-1
          */
-        void onDrawerSlide(View drawerView, float slideOffset);
+        void onDrawerSlide(@NonNull View drawerView, float slideOffset);
 
         /**
          * Called when a drawer has settled in a completely open state.
@@ -256,14 +256,14 @@
          *
          * @param drawerView Drawer view that is now open
          */
-        void onDrawerOpened(View drawerView);
+        void onDrawerOpened(@NonNull View drawerView);
 
         /**
          * Called when a drawer has settled in a completely closed state.
          *
          * @param drawerView Drawer view that is now closed
          */
-        void onDrawerClosed(View drawerView);
+        void onDrawerClosed(@NonNull View drawerView);
 
         /**
          * Called when the drawer motion state changes. The new state will
@@ -296,15 +296,15 @@
         }
     }
 
-    public DrawerLayout(Context context) {
+    public DrawerLayout(@NonNull Context context) {
         this(context, null);
     }
 
-    public DrawerLayout(Context context, AttributeSet attrs) {
+    public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
+    public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
         final float density = getResources().getDisplayMetrics().density;
@@ -626,7 +626,7 @@
      * @see #LOCK_MODE_LOCKED_CLOSED
      * @see #LOCK_MODE_LOCKED_OPEN
      */
-    public void setDrawerLockMode(@LockMode int lockMode, View drawerView) {
+    public void setDrawerLockMode(@LockMode int lockMode, @NonNull View drawerView) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a "
                     + "drawer with appropriate layout_gravity");
@@ -700,7 +700,7 @@
      *         {@link #LOCK_MODE_LOCKED_OPEN}.
      */
     @LockMode
-    public int getDrawerLockMode(View drawerView) {
+    public int getDrawerLockMode(@NonNull View drawerView) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a drawer");
         }
@@ -718,7 +718,7 @@
      *            drawer to set the title for.
      * @param title The title for the drawer.
      */
-    public void setDrawerTitle(@EdgeGravity int edgeGravity, CharSequence title) {
+    public void setDrawerTitle(@EdgeGravity int edgeGravity, @Nullable CharSequence title) {
         final int absGravity = GravityCompat.getAbsoluteGravity(
                 edgeGravity, ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -1276,7 +1276,7 @@
      *
      * @param bg Background drawable to draw behind the status bar
      */
-    public void setStatusBarBackground(Drawable bg) {
+    public void setStatusBarBackground(@Nullable Drawable bg) {
         mStatusBarBackground = bg;
         invalidate();
     }
@@ -1286,6 +1286,7 @@
      *
      * @return The status bar background drawable, or null if none set
      */
+    @Nullable
     public Drawable getStatusBarBackgroundDrawable() {
         return mStatusBarBackground;
     }
@@ -1577,7 +1578,7 @@
      *
      * @param drawerView Drawer view to open
      */
-    public void openDrawer(View drawerView) {
+    public void openDrawer(@NonNull View drawerView) {
         openDrawer(drawerView, true);
     }
 
@@ -1587,7 +1588,7 @@
      * @param drawerView Drawer view to open
      * @param animate Whether opening of the drawer should be animated.
      */
-    public void openDrawer(View drawerView, boolean animate) {
+    public void openDrawer(@NonNull View drawerView, boolean animate) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
         }
@@ -1646,7 +1647,7 @@
      *
      * @param drawerView Drawer view to close
      */
-    public void closeDrawer(View drawerView) {
+    public void closeDrawer(@NonNull View drawerView) {
         closeDrawer(drawerView, true);
     }
 
@@ -1656,7 +1657,7 @@
      * @param drawerView Drawer view to close
      * @param animate Whether closing of the drawer should be animated.
      */
-    public void closeDrawer(View drawerView, boolean animate) {
+    public void closeDrawer(@NonNull View drawerView, boolean animate) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
         }
@@ -1718,7 +1719,7 @@
      * @return true if the given drawer view is in an open state
      * @see #isDrawerVisible(android.view.View)
      */
-    public boolean isDrawerOpen(View drawer) {
+    public boolean isDrawerOpen(@NonNull View drawer) {
         if (!isDrawerView(drawer)) {
             throw new IllegalArgumentException("View " + drawer + " is not a drawer");
         }
@@ -1751,7 +1752,7 @@
      * @return true if the given drawer is visible on-screen
      * @see #isDrawerOpen(android.view.View)
      */
-    public boolean isDrawerVisible(View drawer) {
+    public boolean isDrawerVisible(@NonNull View drawer) {
         if (!isDrawerView(drawer)) {
             throw new IllegalArgumentException("View " + drawer + " is not a drawer");
         }
@@ -2001,7 +2002,7 @@
         @LockMode int lockModeStart;
         @LockMode int lockModeEnd;
 
-        public SavedState(Parcel in, ClassLoader loader) {
+        public SavedState(@NonNull Parcel in, @Nullable ClassLoader loader) {
             super(in, loader);
             openDrawerGravity = in.readInt();
             lockModeLeft = in.readInt();
@@ -2010,7 +2011,7 @@
             lockModeEnd = in.readInt();
         }
 
-        public SavedState(Parcelable superState) {
+        public SavedState(@NonNull Parcelable superState) {
             super(superState);
         }
 
@@ -2218,7 +2219,7 @@
         boolean isPeeking;
         int openState;
 
-        public LayoutParams(Context c, AttributeSet attrs) {
+        public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
             super(c, attrs);
 
             final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
@@ -2235,16 +2236,16 @@
             this.gravity = gravity;
         }
 
-        public LayoutParams(LayoutParams source) {
+        public LayoutParams(@NonNull LayoutParams source) {
             super(source);
             this.gravity = source.gravity;
         }
 
-        public LayoutParams(ViewGroup.LayoutParams source) {
+        public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
             super(source);
         }
 
-        public LayoutParams(ViewGroup.MarginLayoutParams source) {
+        public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
             super(source);
         }
     }
diff --git a/android/support/v4/widget/EdgeEffectCompat.java b/android/support/v4/widget/EdgeEffectCompat.java
index 9293e60..0d370a8 100644
--- a/android/support/v4/widget/EdgeEffectCompat.java
+++ b/android/support/v4/widget/EdgeEffectCompat.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.os.Build;
+import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
 import android.widget.EdgeEffect;
 
@@ -170,7 +171,8 @@
      *
      * @see {@link EdgeEffect#onPull(float, float)}
      */
-    public static void onPull(EdgeEffect edgeEffect, float deltaDistance, float displacement) {
+    public static void onPull(@NonNull EdgeEffect edgeEffect, float deltaDistance,
+            float displacement) {
         IMPL.onPull(edgeEffect, deltaDistance, displacement);
     }
 
diff --git a/android/support/v4/widget/ExploreByTouchHelper.java b/android/support/v4/widget/ExploreByTouchHelper.java
index 8a29eff..2b5ed0a 100644
--- a/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/android/support/v4/widget/ExploreByTouchHelper.java
@@ -129,7 +129,7 @@
      *
      * @param host view whose virtual view hierarchy is exposed by this helper
      */
-    public ExploreByTouchHelper(View host) {
+    public ExploreByTouchHelper(@NonNull View host) {
         if (host == null) {
             throw new IllegalArgumentException("View may not be null");
         }
@@ -1107,7 +1107,8 @@
      *            populate the event
      * @param event The event to populate
      */
-    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+    protected void onPopulateEventForVirtualView(int virtualViewId,
+            @NonNull AccessibilityEvent event) {
         // Default implementation is no-op.
     }
 
@@ -1119,7 +1120,7 @@
      *
      * @param event the event to populate with information about the host view
      */
-    protected void onPopulateEventForHost(AccessibilityEvent event) {
+    protected void onPopulateEventForHost(@NonNull AccessibilityEvent event) {
         // Default implementation is no-op.
     }
 
@@ -1187,7 +1188,7 @@
      * @param node The node to populate
      */
     protected abstract void onPopulateNodeForVirtualView(
-            int virtualViewId, AccessibilityNodeInfoCompat node);
+            int virtualViewId, @NonNull AccessibilityNodeInfoCompat node);
 
     /**
      * Populates an {@link AccessibilityNodeInfoCompat} with information
@@ -1197,7 +1198,7 @@
      *
      * @param node the node to populate with information about the host view
      */
-    protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
+    protected void onPopulateNodeForHost(@NonNull AccessibilityNodeInfoCompat node) {
         // Default implementation is no-op.
     }
 
@@ -1225,7 +1226,7 @@
      * @return true if the action was performed
      */
     protected abstract boolean onPerformActionForVirtualView(
-            int virtualViewId, int action, Bundle arguments);
+            int virtualViewId, int action, @Nullable Bundle arguments);
 
     /**
      * Exposes a virtual view hierarchy to the accessibility framework.
diff --git a/android/support/v4/widget/ImageViewCompat.java b/android/support/v4/widget/ImageViewCompat.java
index acaaf63..b517de5 100644
--- a/android/support/v4/widget/ImageViewCompat.java
+++ b/android/support/v4/widget/ImageViewCompat.java
@@ -20,6 +20,8 @@
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.widget.ImageView;
 
@@ -130,21 +132,24 @@
     /**
      * Return the tint applied to the image drawable, if specified.
      */
-    public static ColorStateList getImageTintList(ImageView view) {
+    @Nullable
+    public static ColorStateList getImageTintList(@NonNull ImageView view) {
         return IMPL.getImageTintList(view);
     }
 
     /**
      * Applies a tint to the image drawable.
      */
-    public static void setImageTintList(ImageView view, ColorStateList tintList) {
+    public static void setImageTintList(@NonNull ImageView view,
+            @Nullable ColorStateList tintList) {
         IMPL.setImageTintList(view, tintList);
     }
 
     /**
      * Return the blending mode used to apply the tint to the image drawable, if specified.
      */
-    public static PorterDuff.Mode getImageTintMode(ImageView view) {
+    @Nullable
+    public static PorterDuff.Mode getImageTintMode(@NonNull ImageView view) {
         return IMPL.getImageTintMode(view);
     }
 
@@ -153,7 +158,7 @@
      * {@link #setImageTintList(android.widget.ImageView, android.content.res.ColorStateList)}
      * to the image drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
      */
-    public static void setImageTintMode(ImageView view, PorterDuff.Mode mode) {
+    public static void setImageTintMode(@NonNull ImageView view, @Nullable PorterDuff.Mode mode) {
         IMPL.setImageTintMode(view, mode);
     }
 
diff --git a/android/support/v4/widget/ListPopupWindowCompat.java b/android/support/v4/widget/ListPopupWindowCompat.java
index ab86e58..4532733 100644
--- a/android/support/v4/widget/ListPopupWindowCompat.java
+++ b/android/support/v4/widget/ListPopupWindowCompat.java
@@ -17,6 +17,8 @@
 package android.support.v4.widget;
 
 import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.view.View;
 import android.view.View.OnTouchListener;
 import android.widget.ListPopupWindow;
@@ -88,8 +90,9 @@
      * @return a touch listener that controls drag-to-open behavior, or {@code null} on
      *         unsupported APIs
      */
+    @Nullable
     public static OnTouchListener createDragToOpenListener(
-            ListPopupWindow listPopupWindow, View src) {
+            @NonNull ListPopupWindow listPopupWindow, @NonNull View src) {
         if (Build.VERSION.SDK_INT >= 19) {
             return listPopupWindow.createDragToOpenListener(src);
         } else {
diff --git a/android/support/v4/widget/ListViewAutoScrollHelper.java b/android/support/v4/widget/ListViewAutoScrollHelper.java
index 73d18ce..c373f27 100644
--- a/android/support/v4/widget/ListViewAutoScrollHelper.java
+++ b/android/support/v4/widget/ListViewAutoScrollHelper.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.widget;
 
+import android.support.annotation.NonNull;
 import android.view.View;
 import android.widget.ListView;
 
@@ -26,7 +27,7 @@
 public class ListViewAutoScrollHelper extends AutoScrollHelper {
     private final ListView mTarget;
 
-    public ListViewAutoScrollHelper(ListView target) {
+    public ListViewAutoScrollHelper(@NonNull ListView target) {
         super(target);
 
         mTarget = target;
diff --git a/android/support/v4/widget/NestedScrollView.java b/android/support/v4/widget/NestedScrollView.java
index 517686f..73ff084 100644
--- a/android/support/v4/widget/NestedScrollView.java
+++ b/android/support/v4/widget/NestedScrollView.java
@@ -26,6 +26,8 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.InputDeviceCompat;
@@ -181,15 +183,16 @@
 
     private OnScrollChangeListener mOnScrollChangeListener;
 
-    public NestedScrollView(Context context) {
+    public NestedScrollView(@NonNull Context context) {
         this(context, null);
     }
 
-    public NestedScrollView(Context context, AttributeSet attrs) {
+    public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         initScrollView();
 
@@ -441,7 +444,7 @@
      * @see android.view.View#getScrollX()
      * @see android.view.View#getScrollY()
      */
-    public void setOnScrollChangeListener(OnScrollChangeListener l) {
+    public void setOnScrollChangeListener(@Nullable OnScrollChangeListener l) {
         mOnScrollChangeListener = l;
     }
 
@@ -552,7 +555,7 @@
      * @param event The key event to execute.
      * @return Return true if the event was handled, else false.
      */
-    public boolean executeKeyEvent(KeyEvent event) {
+    public boolean executeKeyEvent(@NonNull KeyEvent event) {
         mTempRect.setEmpty();
 
         if (!canScroll()) {
diff --git a/android/support/v4/widget/PopupMenuCompat.java b/android/support/v4/widget/PopupMenuCompat.java
index 639a84b..10c5ff3 100644
--- a/android/support/v4/widget/PopupMenuCompat.java
+++ b/android/support/v4/widget/PopupMenuCompat.java
@@ -17,6 +17,8 @@
 package android.support.v4.widget;
 
 import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.view.View.OnTouchListener;
 import android.widget.PopupMenu;
 
@@ -47,7 +49,8 @@
      * @return a touch listener that controls drag-to-open behavior, or {@code null} on
      *         unsupported APIs
      */
-    public static OnTouchListener getDragToOpenListener(Object popupMenu) {
+    @Nullable
+    public static OnTouchListener getDragToOpenListener(@NonNull Object popupMenu) {
         if (Build.VERSION.SDK_INT >= 19) {
             return ((PopupMenu) popupMenu).getDragToOpenListener();
         } else {
diff --git a/android/support/v4/widget/PopupWindowCompat.java b/android/support/v4/widget/PopupWindowCompat.java
index d846b40..d9de3db 100644
--- a/android/support/v4/widget/PopupWindowCompat.java
+++ b/android/support/v4/widget/PopupWindowCompat.java
@@ -17,6 +17,7 @@
 package android.support.v4.widget;
 
 import android.os.Build;
+import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
@@ -213,8 +214,8 @@
      * @param yoff A vertical offset from the anchor in pixels
      * @param gravity Alignment of the popup relative to the anchor
      */
-    public static void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
-            int gravity) {
+    public static void showAsDropDown(@NonNull PopupWindow popup, @NonNull View anchor,
+            int xoff, int yoff, int gravity) {
         IMPL.showAsDropDown(popup, anchor, xoff, yoff, gravity);
     }
 
@@ -224,7 +225,7 @@
      *
      * @param overlapAnchor Whether the popup should overlap its anchor.
      */
-    public static void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
+    public static void setOverlapAnchor(@NonNull PopupWindow popupWindow, boolean overlapAnchor) {
         IMPL.setOverlapAnchor(popupWindow, overlapAnchor);
     }
 
@@ -234,7 +235,7 @@
      *
      * @return Whether the popup should overlap its anchor.
      */
-    public static boolean getOverlapAnchor(PopupWindow popupWindow) {
+    public static boolean getOverlapAnchor(@NonNull PopupWindow popupWindow) {
         return IMPL.getOverlapAnchor(popupWindow);
     }
 
@@ -247,7 +248,7 @@
      *
      * @see android.view.WindowManager.LayoutParams#type
      */
-    public static void setWindowLayoutType(PopupWindow popupWindow, int layoutType) {
+    public static void setWindowLayoutType(@NonNull PopupWindow popupWindow, int layoutType) {
         IMPL.setWindowLayoutType(popupWindow, layoutType);
     }
 
@@ -256,7 +257,7 @@
      *
      * @see #setWindowLayoutType(PopupWindow popupWindow, int)
      */
-    public static int getWindowLayoutType(PopupWindow popupWindow) {
+    public static int getWindowLayoutType(@NonNull PopupWindow popupWindow) {
         return IMPL.getWindowLayoutType(popupWindow);
     }
 }
diff --git a/android/support/v4/widget/SlidingPaneLayout.java b/android/support/v4/widget/SlidingPaneLayout.java
index 602df70..5676ccf 100644
--- a/android/support/v4/widget/SlidingPaneLayout.java
+++ b/android/support/v4/widget/SlidingPaneLayout.java
@@ -30,6 +30,8 @@
 import android.os.Parcelable;
 import android.support.annotation.ColorInt;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.AbsSavedState;
@@ -213,20 +215,20 @@
          * @param panel The child view that was moved
          * @param slideOffset The new offset of this sliding pane within its range, from 0-1
          */
-        void onPanelSlide(View panel, float slideOffset);
+        void onPanelSlide(@NonNull View panel, float slideOffset);
         /**
          * Called when a sliding pane becomes slid completely open. The pane may or may not
          * be interactive at this point depending on how much of the pane is visible.
          * @param panel The child view that was slid to an open position, revealing other panes
          */
-        void onPanelOpened(View panel);
+        void onPanelOpened(@NonNull View panel);
 
         /**
          * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
          * to be interactive. It may now obscure other views in the layout.
          * @param panel The child view that was slid to a closed position
          */
-        void onPanelClosed(View panel);
+        void onPanelClosed(@NonNull View panel);
     }
 
     /**
@@ -245,15 +247,15 @@
         }
     }
 
-    public SlidingPaneLayout(Context context) {
+    public SlidingPaneLayout(@NonNull Context context) {
         this(context, null);
     }
 
-    public SlidingPaneLayout(Context context, AttributeSet attrs) {
+    public SlidingPaneLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public SlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
+    public SlidingPaneLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
         final float density = context.getResources().getDisplayMetrics().density;
@@ -324,7 +326,7 @@
         return mCoveredFadeColor;
     }
 
-    public void setPanelSlideListener(PanelSlideListener listener) {
+    public void setPanelSlideListener(@Nullable PanelSlideListener listener) {
         mPanelSlideListener = listener;
     }
 
@@ -1081,7 +1083,7 @@
      *
      * @param d drawable to use as a shadow
      */
-    public void setShadowDrawableLeft(Drawable d) {
+    public void setShadowDrawableLeft(@Nullable Drawable d) {
         mShadowDrawableLeft = d;
     }
 
@@ -1091,7 +1093,7 @@
      *
      * @param d drawable to use as a shadow
      */
-    public void setShadowDrawableRight(Drawable d) {
+    public void setShadowDrawableRight(@Nullable Drawable d) {
         mShadowDrawableRight = d;
     }
 
@@ -1410,20 +1412,20 @@
             super(width, height);
         }
 
-        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
+        public LayoutParams(@NonNull android.view.ViewGroup.LayoutParams source) {
             super(source);
         }
 
-        public LayoutParams(MarginLayoutParams source) {
+        public LayoutParams(@NonNull MarginLayoutParams source) {
             super(source);
         }
 
-        public LayoutParams(LayoutParams source) {
+        public LayoutParams(@NonNull LayoutParams source) {
             super(source);
             this.weight = source.weight;
         }
 
-        public LayoutParams(Context c, AttributeSet attrs) {
+        public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
             super(c, attrs);
 
             final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
diff --git a/android/support/v4/widget/Space.java b/android/support/v4/widget/Space.java
index 77a2d2e..7d37a72 100644
--- a/android/support/v4/widget/Space.java
+++ b/android/support/v4/widget/Space.java
@@ -19,6 +19,8 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.View;
 
@@ -28,18 +30,18 @@
  */
 public class Space extends View {
 
-    public Space(Context context, AttributeSet attrs, int defStyle) {
+    public Space(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         if (getVisibility() == VISIBLE) {
             setVisibility(INVISIBLE);
         }
     }
 
-    public Space(Context context, AttributeSet attrs) {
+    public Space(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public Space(Context context) {
+    public Space(@NonNull Context context) {
         this(context, null);
     }
 
diff --git a/android/support/v4/widget/SwipeRefreshLayout.java b/android/support/v4/widget/SwipeRefreshLayout.java
index d36ae22..ca04e46 100644
--- a/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/android/support/v4/widget/SwipeRefreshLayout.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.support.annotation.ColorInt;
 import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.ContextCompat;
@@ -316,7 +317,7 @@
      *
      * @param context
      */
-    public SwipeRefreshLayout(Context context) {
+    public SwipeRefreshLayout(@NonNull Context context) {
         this(context, null);
     }
 
@@ -326,7 +327,7 @@
      * @param context
      * @param attrs
      */
-    public SwipeRefreshLayout(Context context, AttributeSet attrs) {
+    public SwipeRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
 
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -341,7 +342,7 @@
         mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
 
         createProgressView();
-        ViewCompat.setChildrenDrawingOrderEnabled(this, true);
+        setChildrenDrawingOrderEnabled(true);
         // the absolute offset has to take into account that the circle starts at an offset
         mSpinnerOffsetEnd = (int) (DEFAULT_CIRCLE_TARGET * metrics.density);
         mTotalDragDistance = mSpinnerOffsetEnd;
@@ -387,7 +388,7 @@
      * Set the listener to be notified when a refresh is triggered via the swipe
      * gesture.
      */
-    public void setOnRefreshListener(OnRefreshListener listener) {
+    public void setOnRefreshListener(@Nullable OnRefreshListener listener) {
         mListener = listener;
     }
 
@@ -1189,6 +1190,6 @@
          *
          * @return Whether it is possible for the child view of parent layout to scroll up.
          */
-        boolean canChildScrollUp(SwipeRefreshLayout parent, @Nullable View child);
+        boolean canChildScrollUp(@NonNull SwipeRefreshLayout parent, @Nullable View child);
     }
 }
diff --git a/android/support/v4/widget/TextViewCompat.java b/android/support/v4/widget/TextViewCompat.java
index 8d9e4ab..dc87a38 100644
--- a/android/support/v4/widget/TextViewCompat.java
+++ b/android/support/v4/widget/TextViewCompat.java
@@ -479,6 +479,7 @@
     /**
      * Returns drawables for the start, top, end, and bottom borders from the given text view.
      */
+    @NonNull
     public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
         return IMPL.getCompoundDrawablesRelative(textView);
     }
@@ -493,7 +494,8 @@
      *
      * @attr name android:autoSizeTextType
      */
-    public static void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) {
+    public static void setAutoSizeTextTypeWithDefaults(@NonNull TextView textView,
+            int autoSizeTextType) {
         IMPL.setAutoSizeTextTypeWithDefaults(textView, autoSizeTextType);
     }
 
@@ -519,7 +521,7 @@
      * @attr name android:autoSizeStepGranularity
      */
     public static void setAutoSizeTextTypeUniformWithConfiguration(
-            TextView textView,
+            @NonNull TextView textView,
             int autoSizeMinTextSize,
             int autoSizeMaxTextSize,
             int autoSizeStepGranularity,
@@ -542,7 +544,7 @@
      * @attr name android:autoSizeTextType
      * @attr name android:autoSizePresetSizes
      */
-    public static void setAutoSizeTextTypeUniformWithPresetSizes(TextView textView,
+    public static void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull TextView textView,
             @NonNull int[] presetSizes, int unit) throws IllegalArgumentException {
         IMPL.setAutoSizeTextTypeUniformWithPresetSizes(textView, presetSizes, unit);
     }
@@ -556,7 +558,7 @@
      *
      * @attr name android:autoSizeTextType
      */
-    public static int getAutoSizeTextType(TextView textView) {
+    public static int getAutoSizeTextType(@NonNull TextView textView) {
         return IMPL.getAutoSizeTextType(textView);
     }
 
@@ -565,7 +567,7 @@
      *
      * @attr name android:autoSizeStepGranularity
      */
-    public static int getAutoSizeStepGranularity(TextView textView) {
+    public static int getAutoSizeStepGranularity(@NonNull TextView textView) {
         return IMPL.getAutoSizeStepGranularity(textView);
     }
 
@@ -575,7 +577,7 @@
      *
      * @attr name android:autoSizeMinTextSize
      */
-    public static int getAutoSizeMinTextSize(TextView textView) {
+    public static int getAutoSizeMinTextSize(@NonNull TextView textView) {
         return IMPL.getAutoSizeMinTextSize(textView);
     }
 
@@ -585,7 +587,7 @@
      *
      * @attr name android:autoSizeMaxTextSize
      */
-    public static int getAutoSizeMaxTextSize(TextView textView) {
+    public static int getAutoSizeMaxTextSize(@NonNull TextView textView) {
         return IMPL.getAutoSizeMaxTextSize(textView);
     }
 
@@ -594,7 +596,8 @@
      *
      * @attr name android:autoSizePresetSizes
      */
-    public static int[] getAutoSizeTextAvailableSizes(TextView textView) {
+    @NonNull
+    public static int[] getAutoSizeTextAvailableSizes(@NonNull TextView textView) {
         return IMPL.getAutoSizeTextAvailableSizes(textView);
     }
 }
diff --git a/android/support/v4/widget/ViewDragHelper.java b/android/support/v4/widget/ViewDragHelper.java
index c222c17..09c6f66 100644
--- a/android/support/v4/widget/ViewDragHelper.java
+++ b/android/support/v4/widget/ViewDragHelper.java
@@ -18,6 +18,8 @@
 package android.support.v4.widget;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.view.ViewCompat;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -167,7 +169,9 @@
          * @param dx Change in X position from the last call
          * @param dy Change in Y position from the last call
          */
-        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
+        public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
+                int dy) {
+        }
 
         /**
          * Called when a child view is captured for dragging or settling. The ID of the pointer
@@ -178,7 +182,7 @@
          * @param capturedChild Child view that was captured
          * @param activePointerId Pointer id tracking the child capture
          */
-        public void onViewCaptured(View capturedChild, int activePointerId) {}
+        public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {}
 
         /**
          * Called when the child view is no longer being actively dragged.
@@ -198,7 +202,7 @@
          * @param xvel X velocity of the pointer as it left the screen in pixels per second.
          * @param yvel Y velocity of the pointer as it left the screen in pixels per second.
          */
-        public void onViewReleased(View releasedChild, float xvel, float yvel) {}
+        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {}
 
         /**
          * Called when one of the subscribed edges in the parent view has been touched
@@ -256,7 +260,7 @@
          * @param child Child view to check
          * @return range of horizontal motion in pixels
          */
-        public int getViewHorizontalDragRange(View child) {
+        public int getViewHorizontalDragRange(@NonNull View child) {
             return 0;
         }
 
@@ -267,7 +271,7 @@
          * @param child Child view to check
          * @return range of vertical motion in pixels
          */
-        public int getViewVerticalDragRange(View child) {
+        public int getViewVerticalDragRange(@NonNull View child) {
             return 0;
         }
 
@@ -287,7 +291,7 @@
          * @param pointerId ID of the pointer attempting the capture
          * @return true if capture should be allowed, false otherwise
          */
-        public abstract boolean tryCaptureView(View child, int pointerId);
+        public abstract boolean tryCaptureView(@NonNull View child, int pointerId);
 
         /**
          * Restrict the motion of the dragged child view along the horizontal axis.
@@ -300,7 +304,7 @@
          * @param dx Proposed change in position for left
          * @return The new clamped position for left
          */
-        public int clampViewPositionHorizontal(View child, int left, int dx) {
+        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
             return 0;
         }
 
@@ -315,7 +319,7 @@
          * @param dy Proposed change in position for top
          * @return The new clamped position for top
          */
-        public int clampViewPositionVertical(View child, int top, int dy) {
+        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
             return 0;
         }
     }
@@ -345,7 +349,7 @@
      * @param cb Callback to provide information and receive events
      * @return a new ViewDragHelper instance
      */
-    public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
+    public static ViewDragHelper create(@NonNull ViewGroup forParent, @NonNull Callback cb) {
         return new ViewDragHelper(forParent.getContext(), forParent, cb);
     }
 
@@ -358,7 +362,8 @@
      * @param cb Callback to provide information and receive events
      * @return a new ViewDragHelper instance
      */
-    public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
+    public static ViewDragHelper create(@NonNull ViewGroup forParent, float sensitivity,
+            @NonNull Callback cb) {
         final ViewDragHelper helper = create(forParent, cb);
         helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
         return helper;
@@ -372,7 +377,8 @@
      * @param context Context to initialize config-dependent params from
      * @param forParent Parent view to monitor
      */
-    private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
+    private ViewDragHelper(@NonNull Context context, @NonNull ViewGroup forParent,
+            @NonNull Callback cb) {
         if (forParent == null) {
             throw new IllegalArgumentException("Parent view may not be null");
         }
@@ -458,7 +464,7 @@
      * @param childView Child view to capture
      * @param activePointerId ID of the pointer that is dragging the captured child view
      */
-    public void captureChildView(View childView, int activePointerId) {
+    public void captureChildView(@NonNull View childView, int activePointerId) {
         if (childView.getParent() != mParentView) {
             throw new IllegalArgumentException("captureChildView: parameter must be a descendant "
                     + "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
@@ -473,6 +479,7 @@
     /**
      * @return The currently captured view, or null if no view has been captured.
      */
+    @Nullable
     public View getCapturedView() {
         return mCapturedView;
     }
@@ -537,7 +544,7 @@
      * @param finalTop Final top position of child
      * @return true if animation should continue through {@link #continueSettling(boolean)} calls
      */
-    public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
+    public boolean smoothSlideViewTo(@NonNull View child, int finalLeft, int finalTop) {
         mCapturedView = child;
         mActivePointerId = INVALID_POINTER;
 
@@ -918,7 +925,7 @@
      * @param y Y coordinate of the active touch point
      * @return true if child views of v can be scrolled by delta of dx.
      */
-    protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
+    protected boolean canScroll(@NonNull View v, boolean checkV, int dx, int dy, int x, int y) {
         if (v instanceof ViewGroup) {
             final ViewGroup group = (ViewGroup) v;
             final int scrollX = v.getScrollX();
@@ -948,7 +955,7 @@
      * @param ev MotionEvent provided to onInterceptTouchEvent
      * @return true if the parent view should return true from onInterceptTouchEvent
      */
-    public boolean shouldInterceptTouchEvent(MotionEvent ev) {
+    public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
         final int action = ev.getActionMasked();
         final int actionIndex = ev.getActionIndex();
 
@@ -1082,7 +1089,7 @@
      *
      * @param ev The touch event received by the parent view
      */
-    public void processTouchEvent(MotionEvent ev) {
+    public void processTouchEvent(@NonNull MotionEvent ev) {
         final int action = ev.getActionMasked();
         final int actionIndex = ev.getActionIndex();
 
@@ -1453,7 +1460,7 @@
      * @param y Y position to test in the parent's coordinate system
      * @return true if the supplied view is under the given point, false otherwise
      */
-    public boolean isViewUnder(View view, int x, int y) {
+    public boolean isViewUnder(@Nullable View view, int x, int y) {
         if (view == null) {
             return false;
         }
@@ -1471,6 +1478,7 @@
      * @param y Y position to test in the parent's coordinate system
      * @return The topmost child view under (x, y) or null if none found.
      */
+    @Nullable
     public View findTopChildUnder(int x, int y) {
         final int childCount = mParentView.getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
diff --git a/android/support/v7/app/NotificationCompat.java b/android/support/v7/app/NotificationCompat.java
deleted file mode 100644
index 6b2b859..0000000
--- a/android/support/v7/app/NotificationCompat.java
+++ /dev/null
@@ -1,52 +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 android.support.v7.app;
-
-import android.content.Context;
-
-/**
- * An extension of {@link android.support.v4.app.NotificationCompat} which adds additional styles.
- * @deprecated Use {@link android.support.v4.app.NotificationCompat}.
- */
-@Deprecated
-public class NotificationCompat extends android.support.v4.app.NotificationCompat {
-
-    /**
-     * @deprecated Use the static classes in {@link android.support.v4.app.NotificationCompat}.
-     */
-    @Deprecated
-    public NotificationCompat() {
-    }
-
-    /**
-     * @deprecated All {@link android.support.v4.app.NotificationCompat.Style styles} can now be
-     * used with {@link android.support.v4.app.NotificationCompat.Builder}.
-     */
-    @Deprecated
-    public static class Builder extends android.support.v4.app.NotificationCompat.Builder {
-
-        /**
-         * @inheritDoc
-         * @deprecated Use {@link android.support.v4.app.NotificationCompat.Builder
-         * #NotificationCompat.Builder(Context, String)}
-         */
-        @Deprecated
-        public Builder(Context context) {
-            super(context);
-        }
-    }
-}
diff --git a/android/support/v7/graphics/Palette.java b/android/support/v7/graphics/Palette.java
index b7fb054..e716fb5 100644
--- a/android/support/v7/graphics/Palette.java
+++ b/android/support/v7/graphics/Palette.java
@@ -80,7 +80,7 @@
         /**
          * Called when the {@link Palette} has been generated.
          */
-        void onGenerated(Palette palette);
+        void onGenerated(@NonNull Palette palette);
     }
 
     static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112;
@@ -95,7 +95,8 @@
     /**
      * Start generating a {@link Palette} with the returned {@link Builder} instance.
      */
-    public static Builder from(Bitmap bitmap) {
+    @NonNull
+    public static Builder from(@NonNull Bitmap bitmap) {
         return new Builder(bitmap);
     }
 
@@ -104,7 +105,8 @@
      * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
      * list of swatches. Will return null if the {@code swatches} is null.
      */
-    public static Palette from(List<Swatch> swatches) {
+    @NonNull
+    public static Palette from(@NonNull List<Swatch> swatches) {
         return new Builder(swatches).generate();
     }
 
@@ -484,6 +486,7 @@
          *     hsv[1] is Saturation [0...1]
          *     hsv[2] is Lightness [0...1]
          */
+        @NonNull
         public float[] getHsl() {
             if (mHsl == null) {
                 mHsl = new float[3];
@@ -610,7 +613,7 @@
         /**
          * Construct a new {@link Builder} using a source {@link Bitmap}
          */
-        public Builder(Bitmap bitmap) {
+        public Builder(@NonNull Bitmap bitmap) {
             if (bitmap == null || bitmap.isRecycled()) {
                 throw new IllegalArgumentException("Bitmap is not valid");
             }
@@ -631,7 +634,7 @@
          * Construct a new {@link Builder} using a list of {@link Swatch} instances.
          * Typically only used for testing.
          */
-        public Builder(List<Swatch> swatches) {
+        public Builder(@NonNull List<Swatch> swatches) {
             if (swatches == null || swatches.isEmpty()) {
                 throw new IllegalArgumentException("List of Swatches is not valid");
             }
@@ -850,7 +853,8 @@
          * generated.
          */
         @NonNull
-        public AsyncTask<Bitmap, Void, Palette> generate(final PaletteAsyncListener listener) {
+        public AsyncTask<Bitmap, Void, Palette> generate(
+                @NonNull final PaletteAsyncListener listener) {
             if (listener == null) {
                 throw new IllegalArgumentException("listener can not be null");
             }
@@ -943,7 +947,7 @@
          *
          * @see Builder#addFilter(Filter)
          */
-        boolean isAllowed(int rgb, float[] hsl);
+        boolean isAllowed(@ColorInt int rgb, @NonNull float[] hsl);
     }
 
     /**
diff --git a/android/support/v7/graphics/Target.java b/android/support/v7/graphics/Target.java
index 640970b..0eff90b 100644
--- a/android/support/v7/graphics/Target.java
+++ b/android/support/v7/graphics/Target.java
@@ -17,6 +17,7 @@
 package android.support.v7.graphics;
 
 import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
 
 /**
  * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances
@@ -122,7 +123,7 @@
         setDefaultWeights();
     }
 
-    Target(Target from) {
+    Target(@NonNull Target from) {
         System.arraycopy(from.mSaturationTargets, 0, mSaturationTargets, 0,
                 mSaturationTargets.length);
         System.arraycopy(from.mLightnessTargets, 0, mLightnessTargets, 0,
@@ -295,13 +296,14 @@
         /**
          * Create a new builder based on an existing {@link Target}.
          */
-        public Builder(Target target) {
+        public Builder(@NonNull Target target) {
             mTarget = new Target(target);
         }
 
         /**
          * Set the minimum saturation value for this target.
          */
+        @NonNull
         public Builder setMinimumSaturation(@FloatRange(from = 0, to = 1) float value) {
             mTarget.mSaturationTargets[INDEX_MIN] = value;
             return this;
@@ -310,6 +312,7 @@
         /**
          * Set the target/ideal saturation value for this target.
          */
+        @NonNull
         public Builder setTargetSaturation(@FloatRange(from = 0, to = 1) float value) {
             mTarget.mSaturationTargets[INDEX_TARGET] = value;
             return this;
@@ -318,6 +321,7 @@
         /**
          * Set the maximum saturation value for this target.
          */
+        @NonNull
         public Builder setMaximumSaturation(@FloatRange(from = 0, to = 1) float value) {
             mTarget.mSaturationTargets[INDEX_MAX] = value;
             return this;
@@ -326,6 +330,7 @@
         /**
          * Set the minimum lightness value for this target.
          */
+        @NonNull
         public Builder setMinimumLightness(@FloatRange(from = 0, to = 1) float value) {
             mTarget.mLightnessTargets[INDEX_MIN] = value;
             return this;
@@ -334,6 +339,7 @@
         /**
          * Set the target/ideal lightness value for this target.
          */
+        @NonNull
         public Builder setTargetLightness(@FloatRange(from = 0, to = 1) float value) {
             mTarget.mLightnessTargets[INDEX_TARGET] = value;
             return this;
@@ -342,6 +348,7 @@
         /**
          * Set the maximum lightness value for this target.
          */
+        @NonNull
         public Builder setMaximumLightness(@FloatRange(from = 0, to = 1) float value) {
             mTarget.mLightnessTargets[INDEX_MAX] = value;
             return this;
@@ -358,6 +365,7 @@
          *
          * @see #setTargetSaturation(float)
          */
+        @NonNull
         public Builder setSaturationWeight(@FloatRange(from = 0) float weight) {
             mTarget.mWeights[INDEX_WEIGHT_SAT] = weight;
             return this;
@@ -374,6 +382,7 @@
          *
          * @see #setTargetLightness(float)
          */
+        @NonNull
         public Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
             mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight;
             return this;
@@ -389,6 +398,7 @@
          * <p>A weight of 0 means that it has no weight, and thus has no
          * bearing on the selection.</p>
          */
+        @NonNull
         public Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
             mTarget.mWeights[INDEX_WEIGHT_POP] = weight;
             return this;
@@ -401,6 +411,7 @@
          * @param exclusive true if any the color is exclusive to this target, or false is the
          *                  color can be selected for other targets.
          */
+        @NonNull
         public Builder setExclusive(boolean exclusive) {
             mTarget.mIsExclusive = exclusive;
             return this;
@@ -409,6 +420,7 @@
         /**
          * Builds and returns the resulting {@link Target}.
          */
+        @NonNull
         public Target build() {
             return mTarget;
         }
diff --git a/android/support/v7/preference/Preference.java b/android/support/v7/preference/Preference.java
index cfc4311..fa8461d 100644
--- a/android/support/v7/preference/Preference.java
+++ b/android/support/v7/preference/Preference.java
@@ -31,7 +31,6 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.content.ContextCompat;
-import android.support.v4.content.SharedPreferencesCompat;
 import android.support.v4.content.res.TypedArrayUtils;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.text.TextUtils;
@@ -1543,7 +1542,7 @@
 
     private void tryCommit(@NonNull SharedPreferences.Editor editor) {
         if (mPreferenceManager.shouldCommit()) {
-            SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
+            editor.apply();
         }
     }
 
diff --git a/android/support/v7/preference/PreferenceFragmentCompat.java b/android/support/v7/preference/PreferenceFragmentCompat.java
index 4fb9ff8..6094217 100644
--- a/android/support/v7/preference/PreferenceFragmentCompat.java
+++ b/android/support/v7/preference/PreferenceFragmentCompat.java
@@ -92,13 +92,13 @@
  * <p>The following sample code shows a simple preference fragment that is
  * populated from a resource.  The resource it loads is:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
  *
  * <p>The fragment implementation itself simply populates the preferences
  * when created.  Note that the preferences framework takes care of loading
  * the current values out of the app preferences and writing them when changed:</p>
  *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferencesCompat.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesCompat.java
  *      support_fragment_compat}
  *
  * @see Preference
@@ -321,7 +321,7 @@
     }
 
     @Override
-    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
         if (mHavePrefs) {
diff --git a/android/support/v7/preference/PreferenceManager.java b/android/support/v7/preference/PreferenceManager.java
index 83af86c..19b6908 100644
--- a/android/support/v7/preference/PreferenceManager.java
+++ b/android/support/v7/preference/PreferenceManager.java
@@ -25,7 +25,6 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.content.ContextCompat;
-import android.support.v4.content.SharedPreferencesCompat;
 import android.text.TextUtils;
 
 /**
@@ -464,10 +463,9 @@
             pm.setSharedPreferencesMode(sharedPreferencesMode);
             pm.inflateFromResource(context, resId, null);
 
-            SharedPreferences.Editor editor =
-                    defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
-
-            SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
+            defaultValueSp.edit()
+                    .putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true)
+                    .apply();
         }
     }
 
@@ -511,7 +509,7 @@
 
     private void setNoCommit(boolean noCommit) {
         if (!noCommit && mEditor != null) {
-            SharedPreferencesCompat.EditorCompat.getInstance().apply(mEditor);
+            mEditor.apply();
         }
         mNoCommit = noCommit;
     }
diff --git a/android/support/v7/recyclerview/extensions/ListAdapter.java b/android/support/v7/recyclerview/extensions/ListAdapter.java
index e08cb53..8b28072 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapter.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapter.java
@@ -66,27 +66,21 @@
  *     public void onBindViewHolder(UserViewHolder holder, int position) {
  *         holder.bindTo(getItem(position));
  *     }
- * }
- *
- * {@literal @}Entity
- * class User {
- *      // ... simple POJO code omitted ...
- *
- *      public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
- *          {@literal @}Override
- *          public boolean areItemsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // User properties may have changed if reloaded from the DB, but ID is fixed
- *              return oldUser.getId() == newUser.getId();
- *          }
- *          {@literal @}Override
- *          public boolean areContentsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // NOTE: if you use equals, your object must properly override Object#equals()
- *              // Incorrectly returning false here will result in too many animations.
- *              return oldUser.equals(newUser);
- *          }
- *      }
+ *     public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
+ *         {@literal @}Override
+ *         public boolean areItemsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // User properties may have changed if reloaded from the DB, but ID is fixed
+ *             return oldUser.getId() == newUser.getId();
+ *         }
+ *         {@literal @}Override
+ *         public boolean areContentsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // NOTE: if you use equals, your object must properly override Object#equals()
+ *             // Incorrectly returning false here will result in too many animations.
+ *             return oldUser.equals(newUser);
+ *         }
+ *     }
  * }</pre>
  *
  * Advanced users that wish for more control over adapter behavior, or to provide a specific base
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterConfig.java b/android/support/v7/recyclerview/extensions/ListAdapterConfig.java
index f861242..25697a1 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapterConfig.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapterConfig.java
@@ -16,7 +16,7 @@
 
 package android.support.v7.recyclerview.extensions;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
 
 import java.util.concurrent.Executor;
 
@@ -118,10 +118,10 @@
                 throw new IllegalArgumentException("Must provide a diffCallback");
             }
             if (mBackgroundThreadExecutor == null) {
-                mBackgroundThreadExecutor = AppToolkitTaskExecutor.getIOThreadExecutor();
+                mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
             }
             if (mMainThreadExecutor == null) {
-                mMainThreadExecutor = AppToolkitTaskExecutor.getMainThreadExecutor();
+                mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
             }
             return new ListAdapterConfig<>(
                     mMainThreadExecutor,
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
index b47b833..d0c7bb3 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
@@ -84,27 +84,21 @@
  *         User user = mHelper.getItem(position);
  *         holder.bindTo(user);
  *     }
- * }
- *
- * {@literal @}Entity
- * class User {
- *      // ... simple POJO code omitted ...
- *
- *      public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
- *          {@literal @}Override
- *          public boolean areItemsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // User properties may have changed if reloaded from the DB, but ID is fixed
- *              return oldUser.getId() == newUser.getId();
- *          }
- *          {@literal @}Override
- *          public boolean areContentsTheSame(
- *                  {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *              // NOTE: if you use equals, your object must properly override Object#equals()
- *              // Incorrectly returning false here will result in too many animations.
- *              return oldUser.equals(newUser);
- *          }
- *      }
+ *     public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
+ *         {@literal @}Override
+ *         public boolean areItemsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // User properties may have changed if reloaded from the DB, but ID is fixed
+ *             return oldUser.getId() == newUser.getId();
+ *         }
+ *         {@literal @}Override
+ *         public boolean areContentsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // NOTE: if you use equals, your object must properly override Object#equals()
+ *             // Incorrectly returning false here will result in too many animations.
+ *             return oldUser.equals(newUser);
+ *         }
+ *     }
  * }</pre>
  *
  * @param <T> Type of the lists this helper will receive.
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index 75fa38f..51510aa 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -214,7 +214,7 @@
                     : R.styleable.TextAppearance_fontFamily;
             if (!context.isRestricted()) {
                 try {
-                    mFontTypeface = a.getFont(fontFamilyId, mStyle, mView);
+                    mFontTypeface = a.getFont(fontFamilyId, mStyle);
                 } catch (UnsupportedOperationException | Resources.NotFoundException e) {
                     // Expected if it is not a font resource.
                 }
diff --git a/android/support/v7/widget/CardView.java b/android/support/v7/widget/CardView.java
index 3df45d9..58a04f0 100644
--- a/android/support/v7/widget/CardView.java
+++ b/android/support/v7/widget/CardView.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.cardview.R;
 import android.util.AttributeSet;
@@ -106,17 +107,17 @@
 
     final Rect mShadowBounds = new Rect();
 
-    public CardView(Context context) {
+    public CardView(@NonNull Context context) {
         super(context);
         initialize(context, null, 0);
     }
 
-    public CardView(Context context, AttributeSet attrs) {
+    public CardView(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         initialize(context, attrs, 0);
     }
 
-    public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public CardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         initialize(context, attrs, defStyleAttr);
     }
@@ -300,6 +301,7 @@
      *
      * @return The background color state list of the CardView.
      */
+    @NonNull
     public ColorStateList getCardBackgroundColor() {
         return IMPL.getBackgroundColor(mCardViewDelegate);
     }
diff --git a/android/support/v7/widget/TintTypedArray.java b/android/support/v7/widget/TintTypedArray.java
index 2213dd3..2270955 100644
--- a/android/support/v7/widget/TintTypedArray.java
+++ b/android/support/v7/widget/TintTypedArray.java
@@ -25,7 +25,6 @@
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
@@ -34,7 +33,6 @@
 import android.support.v7.content.res.AppCompatResources;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.widget.TextView;
 
 /**
  * A class that wraps a {@link android.content.res.TypedArray} and provides the same public API
@@ -98,9 +96,9 @@
      *
      * @param index Index of attribute to retrieve.
      * @param style A style value used for selecting best match font from the list of family. Note
-     * that this value will be ignored if the platform supports font family(API 24 or later).
-     * @param targetView A text view to be applied this font. If async loading is specified in XML,
-     * this view will be refreshed with result typeface.
+     * that this value will be ignored if the platform supports font family (API 24 or later).
+     * @param fontCallback A callback to receive async fetching of this font. If async loading is
+     *                     specified in XML, this callback will be triggered.
      *
      * @return Typeface for the attribute, or {@code null} if not defined.
      * @throws RuntimeException if the TypedArray has already been recycled.
@@ -108,7 +106,7 @@
      *         not a font resource.
      */
     @Nullable
-    public Typeface getFont(@StyleableRes int index, int style, @NonNull TextView targetView) {
+    public Typeface getFont(@StyleableRes int index, int style) {
         final int resourceId = mWrapped.getResourceId(index, 0);
         if (resourceId == 0) {
             return null;
@@ -116,7 +114,7 @@
         if (mTypedValue == null) {
             mTypedValue = new TypedValue();
         }
-        return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, targetView);
+        return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
     }
 
     public int length() {
diff --git a/android/support/v7/widget/helper/ItemTouchHelper.java b/android/support/v7/widget/helper/ItemTouchHelper.java
index b0a2cb3..aee48df 100644
--- a/android/support/v7/widget/helper/ItemTouchHelper.java
+++ b/android/support/v7/widget/helper/ItemTouchHelper.java
@@ -292,6 +292,11 @@
      */
     GestureDetectorCompat mGestureDetector;
 
+    /**
+     * Callback for when long press occurs.
+     */
+    private ItemTouchHelperGestureListener mItemTouchHelperGestureListener;
+
     private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
         @Override
         public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
@@ -468,7 +473,7 @@
         mRecyclerView.addItemDecoration(this);
         mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
         mRecyclerView.addOnChildAttachStateChangeListener(this);
-        initGestureDetector();
+        startGestureDetection();
     }
 
     private void destroyCallbacks() {
@@ -485,14 +490,23 @@
         mOverdrawChild = null;
         mOverdrawChildPosition = -1;
         releaseVelocityTracker();
+        stopGestureDetection();
     }
 
-    private void initGestureDetector() {
-        if (mGestureDetector != null) {
-            return;
-        }
+    private void startGestureDetection() {
+        mItemTouchHelperGestureListener = new ItemTouchHelperGestureListener();
         mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
-                new ItemTouchHelperGestureListener());
+                mItemTouchHelperGestureListener);
+    }
+
+    private void stopGestureDetection() {
+        if (mItemTouchHelperGestureListener != null) {
+            mItemTouchHelperGestureListener.doNotReactToLongPress();
+            mItemTouchHelperGestureListener = null;
+        }
+        if (mGestureDetector != null) {
+            mGestureDetector = null;
+        }
     }
 
     private void getSelectedDxDy(float[] outPosition) {
@@ -2242,9 +2256,33 @@
 
     private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
 
+        /**
+         * Whether to execute code in response to the the invoking of
+         * {@link ItemTouchHelperGestureListener#onLongPress(MotionEvent)}.
+         *
+         * It is necessary to control this here because
+         * {@link GestureDetector.SimpleOnGestureListener} can only be set on a
+         * {@link GestureDetector} in a GestureDetector's constructor, a GestureDetector will call
+         * onLongPress if an {@link MotionEvent#ACTION_DOWN} event is not followed by another event
+         * that would cancel it (like {@link MotionEvent#ACTION_UP} or
+         * {@link MotionEvent#ACTION_CANCEL}), the long press responding to the long press event
+         * needs to be cancellable to prevent unexpected behavior.
+         *
+         * @see #doNotReactToLongPress()
+         */
+        private boolean mShouldReactToLongPress = true;
+
         ItemTouchHelperGestureListener() {
         }
 
+        /**
+         * Call to prevent executing code in response to
+         * {@link ItemTouchHelperGestureListener#onLongPress(MotionEvent)} being called.
+         */
+        void doNotReactToLongPress() {
+            mShouldReactToLongPress = false;
+        }
+
         @Override
         public boolean onDown(MotionEvent e) {
             return true;
@@ -2252,6 +2290,9 @@
 
         @Override
         public void onLongPress(MotionEvent e) {
+            if (!mShouldReactToLongPress) {
+                return;
+            }
             View child = findChildView(e);
             if (child != null) {
                 ViewHolder vh = mRecyclerView.getChildViewHolder(child);
diff --git a/android/support/wear/ambient/AmbientDelegate.java b/android/support/wear/ambient/AmbientDelegate.java
new file mode 100644
index 0000000..4901290
--- /dev/null
+++ b/android/support/wear/ambient/AmbientDelegate.java
@@ -0,0 +1,207 @@
+/*
+ * 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.wear.ambient;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.google.android.wearable.compat.WearableActivityController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+
+/**
+ * Provides compatibility for ambient mode.
+ */
+final class AmbientDelegate {
+
+    private static final String TAG = "AmbientDelegate";
+
+    private WearableActivityController mWearableController;
+
+    private static boolean sInitAutoResumeEnabledMethod;
+    private static boolean sHasAutoResumeEnabledMethod;
+    private final WearableControllerProvider mWearableControllerProvider;
+    private final AmbientCallback mCallback;
+    private final WeakReference<Activity> mActivity;
+
+    /**
+     * AmbientCallback must be implemented by all users of the delegate.
+     */
+    interface AmbientCallback {
+        /**
+         * Called when an activity is entering ambient mode. This event is sent while an activity is
+         * running (after onResume, before onPause). All drawing should complete by the conclusion
+         * of this method. Note that {@code invalidate()} calls will be executed before resuming
+         * lower-power mode.
+         * <p>
+         * <p><em>Derived classes must call through to the super class's implementation of this
+         * method. If they do not, an exception will be thrown.</em>
+         *
+         * @param ambientDetails bundle containing information about the display being used.
+         *                      It includes information about low-bit color and burn-in protection.
+         */
+        void onEnterAmbient(Bundle ambientDetails);
+
+        /**
+         * Called when the system is updating the display for ambient mode. Activities may use this
+         * opportunity to update or invalidate views.
+         */
+        void onUpdateAmbient();
+
+        /**
+         * Called when an activity should exit ambient mode. This event is sent while an activity is
+         * running (after onResume, before onPause).
+         * <p>
+         * <p><em>Derived classes must call through to the super class's implementation of this
+         * method. If they do not, an exception will be thrown.</em>
+         */
+        void onExitAmbient();
+    }
+
+    AmbientDelegate(@Nullable Activity activity,
+                           @NonNull WearableControllerProvider wearableControllerProvider,
+                           @NonNull AmbientCallback callback) {
+        mActivity = new WeakReference<>(activity);
+        mCallback = callback;
+        mWearableControllerProvider = wearableControllerProvider;
+    }
+
+    /**
+     * Receives and handles the onCreate call from the associated {@link AmbientMode}
+     */
+    void onCreate() {
+        Activity activity = mActivity.get();
+        if (activity != null) {
+            mWearableController =
+                    mWearableControllerProvider.getWearableController(activity, mCallback);
+        }
+        if (mWearableController != null) {
+            mWearableController.onCreate();
+        }
+    }
+
+    /**
+     * Receives and handles the onResume call from the associated {@link AmbientMode}
+     */
+    void onResume() {
+        if (mWearableController != null) {
+            mWearableController.onResume();
+        }
+    }
+
+    /**
+     * Receives and handles the onPause call from the associated {@link AmbientMode}
+     */
+    void onPause() {
+        if (mWearableController != null) {
+            mWearableController.onPause();
+        }
+    }
+
+    /**
+     * Receives and handles the onStop call from the associated {@link AmbientMode}
+     */
+    void onStop() {
+        if (mWearableController != null) {
+            mWearableController.onStop();
+        }
+    }
+
+    /**
+     * Receives and handles the onDestroy call from the associated {@link AmbientMode}
+     */
+    void onDestroy() {
+        if (mWearableController != null) {
+            mWearableController.onDestroy();
+        }
+    }
+
+    /**
+     * Sets that this activity should remain displayed when the system enters ambient mode. The
+     * default is false. In this case, the activity is stopped when the system enters ambient mode.
+     */
+    void setAmbientEnabled() {
+        if (mWearableController != null) {
+            mWearableController.setAmbientEnabled();
+        }
+    }
+
+    /**
+     * 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.
+     */
+    void setAutoResumeEnabled(boolean enabled) {
+        if (mWearableController != null) {
+            if (hasSetAutoResumeEnabledMethod()) {
+                mWearableController.setAutoResumeEnabled(enabled);
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} if the activity is currently in ambient.
+     */
+    boolean isAmbient() {
+        if (mWearableController != null) {
+            return mWearableController.isAmbient();
+        }
+        return false;
+    }
+
+    /**
+     * Dump the current state of the wearableController responsible for implementing the Ambient
+     * mode.
+     */
+    void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mWearableController != null) {
+            mWearableController.dump(prefix, fd, writer, args);
+        }
+    }
+
+    private boolean hasSetAutoResumeEnabledMethod() {
+        if (!sInitAutoResumeEnabledMethod) {
+            sInitAutoResumeEnabledMethod = true;
+            try {
+                Method method =
+                        WearableActivityController.class
+                                .getDeclaredMethod("setAutoResumeEnabled", boolean.class);
+                // Proguard is sneaky -- it will actually rewrite strings it finds in addition to
+                // function names. Therefore add a "." prefix to the method name check to ensure the
+                // function was not renamed by proguard.
+                if (!(".setAutoResumeEnabled".equals("." + method.getName()))) {
+                    throw new NoSuchMethodException();
+                }
+                sHasAutoResumeEnabledMethod = true;
+            } catch (NoSuchMethodException e) {
+                Log.w(
+                        "WearableActivity",
+                        "Could not find a required method for auto-resume "
+                                + "support, likely due to proguard optimization. Please add "
+                                + "com.google.android.wearable:wearable jar to the list of library "
+                                + "jars for your project");
+                sHasAutoResumeEnabledMethod = false;
+            }
+        }
+        return sHasAutoResumeEnabledMethod;
+    }
+}
diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java
new file mode 100644
index 0000000..7fbbbb3
--- /dev/null
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -0,0 +1,286 @@
+/*
+ * 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.wear.ambient;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Context;
+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;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Use this as a headless Fragment to add ambient support to an Activity on Wearable devices.
+ * <p>
+ * The application that uses this should add the {@link android.Manifest.permission#WAKE_LOCK}
+ * permission to its manifest.
+ * <p>
+ * The primary entry  point for this code is the {@link #attachAmbientSupport(Activity)} method.
+ * It should be called with an {@link Activity} as an argument and that {@link Activity} will then
+ * be able to receive ambient lifecycle events through an {@link AmbientCallback}. The
+ * {@link Activity} will also receive a {@link AmbientController} object from the attachment which
+ * can be used to query the current status of the ambient mode, or toggle simple settings.
+ * An example of how to attach {@link AmbientMode} to your {@link Activity} and use
+ * the {@link AmbientController} can be found below:
+ * <p>
+ * <pre class="prettyprint">{@code
+ *     AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
+ *     controller.setAutoResumeEnabled(true);
+ * }</pre>
+ */
+public final class AmbientMode extends Fragment {
+
+    /**
+     * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
+     * whether burn-in protection is required. When this property is set to true, views must be
+     * shifted around periodically in ambient mode. To ensure that content isn't shifted off
+     * the screen, avoid placing content within 10 pixels of the edge of the screen. Activities
+     * should also avoid solid white areas to prevent pixel burn-in. Both of these requirements
+     * only apply in ambient mode, and only when this property is set to true.
+     */
+    public static final String EXTRA_BURN_IN_PROTECTION =
+            WearableActivityController.EXTRA_BURN_IN_PROTECTION;
+
+    /**
+     * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
+     * whether the device has low-bit ambient mode. When this property is set to true, the screen
+     * supports fewer bits for each color in ambient mode. In this case, activities should disable
+     * anti-aliasing in ambient mode.
+     */
+    public static final String EXTRA_LOWBIT_AMBIENT =
+            WearableActivityController.EXTRA_LOWBIT_AMBIENT;
+
+    /**
+     * Fragment tag used by default when adding {@link AmbientMode} to add ambient support to an
+     * {@link Activity}.
+     */
+    public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
+
+    /**
+     * Interface for any {@link Activity} that wishes to implement Ambient Mode. Use the
+     * {@link #getAmbientCallback()} method to return and {@link AmbientCallback} which can be used
+     * to bind the {@link AmbientMode} to the instantiation of this interface.
+     * <p>
+     * <pre class="prettyprint">{@code
+     * return new AmbientMode.AmbientCallback() {
+     *     public void onEnterAmbient(Bundle ambientDetails) {...}
+     *     public void onExitAmbient(Bundle ambientDetails) {...}
+     *  }
+     * }</pre>
+     */
+    public interface AmbientCallbackProvider {
+        /**
+         * @return the {@link AmbientCallback} to be used by this class to communicate with the
+         * entity interested in ambient events.
+         */
+        AmbientCallback getAmbientCallback();
+    }
+
+    /**
+     * Callback to receive ambient mode state changes. It must be used by all users of AmbientMode.
+     */
+    public abstract static class AmbientCallback {
+        /**
+         * Called when an activity is entering ambient mode. This event is sent while an activity is
+         * running (after onResume, before onPause). All drawing should complete by the conclusion
+         * of this method. Note that {@code invalidate()} calls will be executed before resuming
+         * lower-power mode.
+         * <p>
+         * <p><em>Derived classes must call through to the super class's implementation of this
+         * method. If they do not, an exception will be thrown.</em>
+         *
+         * @param ambientDetails bundle containing information about the display being used.
+         *                      It includes information about low-bit color and burn-in protection.
+         */
+        public void onEnterAmbient(Bundle ambientDetails) {}
+
+        /**
+         * Called when the system is updating the display for ambient mode. Activities may use this
+         * opportunity to update or invalidate views.
+         */
+        public void onUpdateAmbient() {};
+
+        /**
+         * Called when an activity should exit ambient mode. This event is sent while an activity is
+         * running (after onResume, before onPause).
+         * <p>
+         * <p><em>Derived classes must call through to the super class's implementation of this
+         * method. If they do not, an exception will be thrown.</em>
+         */
+        public void onExitAmbient() {};
+    }
+
+    private final AmbientDelegate.AmbientCallback mCallback =
+            new AmbientDelegate.AmbientCallback() {
+                @Override
+                public void onEnterAmbient(Bundle ambientDetails) {
+                    mSuppliedCallback.onEnterAmbient(ambientDetails);
+                }
+
+                @Override
+                public void onExitAmbient() {
+                    mSuppliedCallback.onExitAmbient();
+                }
+
+                @Override
+                public void onUpdateAmbient() {
+                    mSuppliedCallback.onUpdateAmbient();
+                }
+            };
+    private AmbientDelegate mDelegate;
+    private AmbientCallback mSuppliedCallback;
+    private AmbientController mController;
+
+    /**
+     * Constructor
+     */
+    public AmbientMode() {
+        mController = new AmbientController();
+    }
+
+    @Override
+    @CallSuper
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mDelegate = new AmbientDelegate(getActivity(), new WearableControllerProvider(), mCallback);
+
+        if (context instanceof AmbientCallbackProvider) {
+            mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
+        } else {
+            throw new IllegalArgumentException(
+                    "fragment should attach to an activity that implements AmbientCallback");
+        }
+    }
+
+    @Override
+    @CallSuper
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mDelegate.onCreate();
+        mDelegate.setAmbientEnabled();
+    }
+
+    @Override
+    @CallSuper
+    public void onResume() {
+        super.onResume();
+        mDelegate.onResume();
+    }
+
+    @Override
+    @CallSuper
+    public void onPause() {
+        mDelegate.onPause();
+        super.onPause();
+    }
+
+    @Override
+    @CallSuper
+    public void onStop() {
+        mDelegate.onStop();
+        super.onStop();
+    }
+
+    @Override
+    @CallSuper
+    public void onDestroy() {
+        mDelegate.onDestroy();
+        super.onDestroy();
+    }
+
+    @Override
+    @CallSuper
+    public void onDetach() {
+        mDelegate = null;
+        super.onDetach();
+    }
+
+    /**
+     * Attach ambient support to the given activity.
+     *
+     * @param activity the activity to attach ambient support to. This activity has to also
+     *                implement {@link AmbientCallbackProvider}
+     * @return the associated {@link AmbientController} which can be used to query the state of
+     * ambient mode and toggle simple settings related to it.
+     */
+    public static <T extends Activity & AmbientCallbackProvider> AmbientController
+            attachAmbientSupport(T activity) {
+        FragmentManager fragmentManager = activity.getFragmentManager();
+        AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+        if (ambientFragment == null) {
+            AmbientMode fragment = new AmbientMode();
+            fragmentManager
+                    .beginTransaction()
+                    .add(fragment, FRAGMENT_TAG)
+                    .commit();
+            ambientFragment = fragment;
+        }
+        return ambientFragment.mController;
+    }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mDelegate != null) {
+            mDelegate.dump(prefix, fd, writer, args);
+        }
+    }
+
+    @VisibleForTesting
+    void setAmbientDelegate(AmbientDelegate delegate) {
+        mDelegate = delegate;
+    }
+
+    /**
+     * A class for interacting with the ambient mode on a wearable device. This class can be used to
+     * query the current state of ambient mode and to enable or disable certain settings.
+     * An instance of this class is returned to the user when they attach their {@link Activity}
+     * to {@link AmbientMode}.
+     */
+    public final class AmbientController {
+        private static final String TAG = "AmbientController";
+
+        // Do not initialize outside of this class.
+        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() {
+            return mDelegate == null ? false : mDelegate.isAmbient();
+        }
+    }
+}
diff --git a/android/support/wear/ambient/SharedLibraryVersion.java b/android/support/wear/ambient/SharedLibraryVersion.java
new file mode 100644
index 0000000..cd90a3b
--- /dev/null
+++ b/android/support/wear/ambient/SharedLibraryVersion.java
@@ -0,0 +1,97 @@
+/*
+ * 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.wear.ambient;
+
+import android.os.Build;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import com.google.android.wearable.WearableSharedLib;
+
+/**
+ * Internal class which can be used to determine the version of the wearable shared library that is
+ * available on the current device.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+final class SharedLibraryVersion {
+
+    private SharedLibraryVersion() {
+    }
+
+    /**
+     * Returns the version of the wearable shared library available on the current device.
+     * <p>
+     * <p>Version 1 was introduced on 2016-09-26, so any previous shared library will return 0. In
+     * those cases, it may be necessary to check {@code Build.VERSION.SDK_INT}.
+     *
+     * @throws IllegalStateException if the Wearable Shared Library is not present, which means that
+     * the {@code <uses-library>} tag is missing.
+     */
+    public static int version() {
+        verifySharedLibraryPresent();
+        return VersionHolder.VERSION;
+    }
+
+    /**
+     * Throws {@link IllegalStateException} if the Wearable Shared Library is not present and API
+     * level is at least LMP MR1.
+     * <p>
+     * <p>This validates that the developer hasn't forgotten to include a {@code <uses-library>} tag
+     * in their manifest. The method should be used in combination with API level checks for
+     * features added before {@link #version() version} 1.
+     */
+    public static void verifySharedLibraryPresent() {
+        if (!PresenceHolder.PRESENT) {
+            throw new IllegalStateException("Could not find wearable shared library classes. "
+                    + "Please add <uses-library android:name=\"com.google.android.wearable\" "
+                    + "android:required=\"false\" /> to the application manifest");
+        }
+    }
+
+    // Lazy initialization holder class (see Effective Java item 71)
+    @VisibleForTesting
+    static final class VersionHolder {
+        static final int VERSION = getSharedLibVersion(Build.VERSION.SDK_INT);
+
+        @VisibleForTesting
+        static int getSharedLibVersion(int sdkInt) {
+            if (sdkInt < Build.VERSION_CODES.N_MR1) {
+                // WearableSharedLib was introduced in N MR1 (Wear FDP 4)
+                return 0;
+            }
+            return WearableSharedLib.version();
+        }
+    }
+
+    // Lazy initialization holder class (see Effective Java item 71)
+    @VisibleForTesting
+    static final class PresenceHolder {
+        static final boolean PRESENT = isSharedLibPresent(Build.VERSION.SDK_INT);
+
+        @VisibleForTesting
+        static boolean isSharedLibPresent(int sdkInt) {
+            try {
+                // A class which has been available on the shared library from the first version.
+                Class.forName("com.google.android.wearable.compat.WearableActivityController");
+            } catch (ClassNotFoundException e) {
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/android/support/wear/ambient/WearableControllerProvider.java b/android/support/wear/ambient/WearableControllerProvider.java
new file mode 100644
index 0000000..1682dc0
--- /dev/null
+++ b/android/support/wear/ambient/WearableControllerProvider.java
@@ -0,0 +1,96 @@
+/*
+ * 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.wear.ambient;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.RestrictTo;
+
+import com.google.android.wearable.compat.WearableActivityController;
+
+import java.lang.reflect.Method;
+
+/**
+ * Provides a {@link WearableActivityController} for ambient mode control.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class WearableControllerProvider {
+
+    private static final String TAG = "WearableControllerProvider";
+
+    private static volatile boolean sAmbientCallbacksVerifiedPresent;
+
+    /**
+     * Retrieves a {@link WearableActivityController} to use for ambient mode.
+     *
+     * @param activity The {@link Activity} to be associated with the Controller.
+     * @param callback The {@link AmbientDelegate.AmbientCallback} for the Controller.
+     * @return the platform-appropriate version of the {@link WearableActivityController}.
+     */
+    public WearableActivityController getWearableController(Activity activity,
+            final AmbientDelegate.AmbientCallback callback) {
+        SharedLibraryVersion.verifySharedLibraryPresent();
+
+        // The AmbientCallback is an abstract class instead of an interface.
+        WearableActivityController.AmbientCallback callbackBridge =
+                new WearableActivityController.AmbientCallback() {
+                    @Override
+                    public void onEnterAmbient(Bundle ambientDetails) {
+                        callback.onEnterAmbient(ambientDetails);
+                    }
+
+                    @Override
+                    public void onUpdateAmbient() {
+                        callback.onUpdateAmbient();
+                    }
+
+                    @Override
+                    public void onExitAmbient() {
+                        callback.onExitAmbient();
+                    }
+                };
+
+        verifyAmbientCallbacksPresent();
+
+        return new WearableActivityController(TAG, activity, callbackBridge);
+    }
+
+    private static void verifyAmbientCallbacksPresent() {
+        if (sAmbientCallbacksVerifiedPresent) {
+            return;
+        }
+        try {
+            Method method =
+                    WearableActivityController.AmbientCallback.class.getDeclaredMethod(
+                            "onEnterAmbient", Bundle.class);
+            // Proguard is sneaky -- it will actually rewrite strings it finds in addition to
+            // function names. Therefore add a "." prefix to the method name check to ensure the
+            // function was not renamed by proguard.
+            if (!(".onEnterAmbient".equals("." + method.getName()))) {
+                throw new NoSuchMethodException();
+            }
+        } catch (NoSuchMethodException e) {
+            throw new IllegalStateException(
+                    "Could not find a required method for "
+                            + "ambient support, likely due to proguard optimization. Please add "
+                            + "com.google.android.wearable:wearable jar to the list of library jars"
+                            + " for your project");
+        }
+        sAmbientCallbacksVerifiedPresent = true;
+    }
+}
diff --git a/android/system/Os.java b/android/system/Os.java
index 5b9ff47..8e312dd 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -516,7 +516,6 @@
 
   /** @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 setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException { Libcore.os.setsockoptGroupSourceReq(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); }
 
diff --git a/android/system/StructGroupSourceReq.java b/android/system/StructGroupSourceReq.java
deleted file mode 100644
index c300338..0000000
--- a/android/system/StructGroupSourceReq.java
+++ /dev/null
@@ -1,41 +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.system;
-
-import java.net.InetAddress;
-import libcore.util.Objects;
-
-/**
- * Corresponds to C's {@code struct group_source_req}.
- *
- * @hide
- */
-public final class StructGroupSourceReq {
-  public final int gsr_interface;
-  public final InetAddress gsr_group;
-  public final InetAddress gsr_source;
-
-  public StructGroupSourceReq(int gsr_interface, InetAddress gsr_group, InetAddress gsr_source) {
-    this.gsr_interface = gsr_interface;
-    this.gsr_group = gsr_group;
-    this.gsr_source = gsr_source;
-  }
-
-  @Override public String toString() {
-    return Objects.toString(this);
-  }
-}
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
index ebac041..764b7b2 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -522,8 +522,7 @@
      * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
      */
     public void registerStateCallback(@NonNull DownloadRequest request,
-            @NonNull DownloadStateCallback callback,
-            @NonNull Handler handler) {
+            @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
             throw new IllegalStateException("Middleware not yet bound");
@@ -533,7 +532,8 @@
                 new InternalDownloadStateCallback(callback, handler);
 
         try {
-            int result = downloadService.registerStateCallback(request, internalCallback);
+            int result = downloadService.registerStateCallback(request, internalCallback,
+                    callback.getCallbackFilterFlags());
             if (result != MbmsErrors.SUCCESS) {
                 if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                     throw new IllegalArgumentException("Unknown download request.");
diff --git a/android/telephony/PhoneNumberUtils.java b/android/telephony/PhoneNumberUtils.java
index d5ff1ad..ff67116 100644
--- a/android/telephony/PhoneNumberUtils.java
+++ b/android/telephony/PhoneNumberUtils.java
@@ -77,9 +77,28 @@
     public static final int TOA_International = 0x91;
     public static final int TOA_Unknown = 0x81;
 
+    /*
+     * The BCD extended type used to determine the extended char for the digit which is greater than
+     * 9.
+     *
+     * see TS 51.011 section 10.5.1 EF_ADN(Abbreviated dialling numbers)
+     */
+    public static final int BCD_EXTENDED_TYPE_EF_ADN = 1;
+
+    /*
+     * The BCD extended type used to determine the extended char for the digit which is greater than
+     * 9.
+     *
+     * see TS 24.008 section 10.5.4.7 Called party BCD number
+     */
+    public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2;
+
     static final String LOG_TAG = "PhoneNumberUtils";
     private static final boolean DBG = false;
 
+    private static final String BCD_EF_ADN_EXTENDED = "*#,N;";
+    private static final String BCD_CALLED_PARTY_EXTENDED = "*#abc";
+
     /*
      * global-phone-number = ["+"] 1*( DIGIT / written-sep )
      * written-sep         = ("-"/".")
@@ -799,11 +818,33 @@
      *
      * @return partial string on invalid decode
      *
-     * FIXME(mkf) support alphanumeric address type
-     *  currently implemented in SMSMessage.getAddress()
+     * @deprecated use {@link #calledPartyBCDToString(byte[], int, int, int)} instead. Calling this
+     * method is equivalent to calling {@link #calledPartyBCDToString(byte[], int, int)} with
+     * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
      */
-    public static String
-    calledPartyBCDToString (byte[] bytes, int offset, int length) {
+    @Deprecated
+    public static String calledPartyBCDToString(byte[] bytes, int offset, int length) {
+        return calledPartyBCDToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
+    }
+
+    /**
+     *  3GPP TS 24.008 10.5.4.7
+     *  Called Party BCD Number
+     *
+     *  See Also TS 51.011 10.5.1 "dialing number/ssc string"
+     *  and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
+     *
+     * @param bytes the data buffer
+     * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
+     * @param length is the number of bytes including TOA byte
+     *                and must be at least 2
+     * @param bcdExtType used to determine the extended bcd coding
+     * @see #BCD_EXTENDED_TYPE_EF_ADN
+     * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
+     *
+     */
+    public static String calledPartyBCDToString(
+            byte[] bytes, int offset, int length, int bcdExtType) {
         boolean prependPlus = false;
         StringBuilder ret = new StringBuilder(1 + length * 2);
 
@@ -817,7 +858,7 @@
         }
 
         internalCalledPartyBCDFragmentToString(
-                ret, bytes, offset + 1, length - 1);
+                ret, bytes, offset + 1, length - 1, bcdExtType);
 
         if (prependPlus && ret.length() == 0) {
             // If the only thing there is a prepended plus, return ""
@@ -902,14 +943,13 @@
         return ret.toString();
     }
 
-    private static void
-    internalCalledPartyBCDFragmentToString(
-        StringBuilder sb, byte [] bytes, int offset, int length) {
+    private static void internalCalledPartyBCDFragmentToString(
+            StringBuilder sb, byte [] bytes, int offset, int length, int bcdExtType) {
         for (int i = offset ; i < length + offset ; i++) {
             byte b;
             char c;
 
-            c = bcdToChar((byte)(bytes[i] & 0xf));
+            c = bcdToChar((byte)(bytes[i] & 0xf), bcdExtType);
 
             if (c == 0) {
                 return;
@@ -930,7 +970,7 @@
                 break;
             }
 
-            c = bcdToChar(b);
+            c = bcdToChar(b, bcdExtType);
             if (c == 0) {
                 return;
             }
@@ -943,49 +983,65 @@
     /**
      * Like calledPartyBCDToString, but field does not start with a
      * TOA byte. For example: SIM ADN extension fields
+     *
+     * @deprecated use {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} instead.
+     * Calling this method is equivalent to calling
+     * {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} with
+     * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
      */
+    @Deprecated
+    public static String calledPartyBCDFragmentToString(byte[] bytes, int offset, int length) {
+        return calledPartyBCDFragmentToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
+    }
 
-    public static String
-    calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
+    /**
+     * Like calledPartyBCDToString, but field does not start with a
+     * TOA byte. For example: SIM ADN extension fields
+     */
+    public static String calledPartyBCDFragmentToString(
+            byte[] bytes, int offset, int length, int bcdExtType) {
         StringBuilder ret = new StringBuilder(length * 2);
-
-        internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
-
+        internalCalledPartyBCDFragmentToString(ret, bytes, offset, length, bcdExtType);
         return ret.toString();
     }
 
-    /** returns 0 on invalid value */
-    private static char
-    bcdToChar(byte b) {
+    /**
+     * Returns the correspond character for given {@code b} based on {@code bcdExtType}, or 0 on
+     * invalid code.
+     */
+    private static char bcdToChar(byte b, int bcdExtType) {
         if (b < 0xa) {
-            return (char)('0' + b);
-        } else switch (b) {
-            case 0xa: return '*';
-            case 0xb: return '#';
-            case 0xc: return PAUSE;
-            case 0xd: return WILD;
-
-            default: return 0;
+            return (char) ('0' + b);
         }
+
+        String extended = null;
+        if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
+            extended = BCD_EF_ADN_EXTENDED;
+        } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
+            extended = BCD_CALLED_PARTY_EXTENDED;
+        }
+        if (extended == null || b - 0xa >= extended.length()) {
+            return 0;
+        }
+
+        return extended.charAt(b - 0xa);
     }
 
-    private static int
-    charToBCD(char c) {
-        if (c >= '0' && c <= '9') {
+    private static int charToBCD(char c, int bcdExtType) {
+        if ('0' <= c && c <= '9') {
             return c - '0';
-        } else if (c == '*') {
-            return 0xa;
-        } else if (c == '#') {
-            return 0xb;
-        } else if (c == PAUSE) {
-            return 0xc;
-        } else if (c == WILD) {
-            return 0xd;
-        } else if (c == WAIT) {
-            return 0xe;
-        } else {
-            throw new RuntimeException ("invalid char for BCD " + c);
         }
+
+        String extended = null;
+        if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
+            extended = BCD_EF_ADN_EXTENDED;
+        } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
+            extended = BCD_CALLED_PARTY_EXTENDED;
+        }
+        if (extended == null || extended.indexOf(c) == -1) {
+            throw new RuntimeException("invalid char for BCD " + c);
+        }
+        return 0xa + extended.indexOf(c);
     }
 
     /**
@@ -1034,40 +1090,60 @@
      *
      * Returns null if network portion is empty.
      */
-    public static byte[]
-    networkPortionToCalledPartyBCD(String s) {
+    public static byte[] networkPortionToCalledPartyBCD(String s) {
         String networkPortion = extractNetworkPortion(s);
-        return numberToCalledPartyBCDHelper(networkPortion, false);
+        return numberToCalledPartyBCDHelper(
+                networkPortion, false, BCD_EXTENDED_TYPE_EF_ADN);
     }
 
     /**
      * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
      * one-byte length prefix.
      */
-    public static byte[]
-    networkPortionToCalledPartyBCDWithLength(String s) {
+    public static byte[] networkPortionToCalledPartyBCDWithLength(String s) {
         String networkPortion = extractNetworkPortion(s);
-        return numberToCalledPartyBCDHelper(networkPortion, true);
+        return numberToCalledPartyBCDHelper(
+                networkPortion, true, BCD_EXTENDED_TYPE_EF_ADN);
     }
 
     /**
      * Convert a dialing number to BCD byte array
      *
-     * @param number dialing number string
-     *        if the dialing number starts with '+', set to international TOA
+     * @param number dialing number string. If the dialing number starts with '+', set to
+     * international TOA
+     *
+     * @return BCD byte array
+     *
+     * @deprecated use {@link #numberToCalledPartyBCD(String, int)} instead. Calling this method
+     * is equivalent to calling {@link #numberToCalledPartyBCD(String, int)} with
+     * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
+     */
+    @Deprecated
+    public static byte[] numberToCalledPartyBCD(String number) {
+        return numberToCalledPartyBCD(number, BCD_EXTENDED_TYPE_EF_ADN);
+    }
+
+    /**
+     * Convert a dialing number to BCD byte array
+     *
+     * @param number dialing number string. If the dialing number starts with '+', set to
+     * international TOA
+     * @param bcdExtType used to determine the extended bcd coding
+     * @see #BCD_EXTENDED_TYPE_EF_ADN
+     * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
+     *
      * @return BCD byte array
      */
-    public static byte[]
-    numberToCalledPartyBCD(String number) {
-        return numberToCalledPartyBCDHelper(number, false);
+    public static byte[] numberToCalledPartyBCD(String number, int bcdExtType) {
+        return numberToCalledPartyBCDHelper(number, false, bcdExtType);
     }
 
     /**
      * If includeLength is true, prepend a one-byte length value to
      * the return array.
      */
-    private static byte[]
-    numberToCalledPartyBCDHelper(String number, boolean includeLength) {
+    private static byte[] numberToCalledPartyBCDHelper(
+            String number, boolean includeLength, int bcdExtType) {
         int numberLenReal = number.length();
         int numberLenEffective = numberLenReal;
         boolean hasPlus = number.indexOf('+') != -1;
@@ -1087,7 +1163,8 @@
             char c = number.charAt(i);
             if (c == '+') continue;
             int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
-            result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
+            result[extraBytes + (digitCount >> 1)] |=
+                    (byte)((charToBCD(c, bcdExtType) & 0x0F) << shift);
             digitCount++;
         }
 
diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java
index cde0bdf..c0564c5 100644
--- a/android/telephony/TelephonyManager.java
+++ b/android/telephony/TelephonyManager.java
@@ -107,8 +107,6 @@
     public static final String MODEM_ACTIVITY_RESULT_KEY =
             BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY;
 
-    private static ITelephonyRegistry sRegistry;
-
     /**
      * The allowed states of Wi-Fi calling.
      *
@@ -179,11 +177,6 @@
             mContext = context;
         }
         mSubscriptionManager = SubscriptionManager.from(mContext);
-
-        if (sRegistry == null) {
-            sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
-                    "telephony.registry"));
-        }
     }
 
     /** @hide */
@@ -3513,6 +3506,10 @@
         return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
     }
 
+    private ITelephonyRegistry getTelephonyRegistry() {
+        return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
+    }
+
     //
     //
     // PhoneStateListener
@@ -3552,12 +3549,16 @@
             if (listener.mSubId == null) {
                 listener.mSubId = mSubId;
             }
-            sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(),
-                    listener.callback, events, notifyNow);
+
+            ITelephonyRegistry registry = getTelephonyRegistry();
+            if (registry != null) {
+                registry.listenForSubscriber(listener.mSubId, getOpPackageName(),
+                        listener.callback, events, notifyNow);
+            } else {
+                Rlog.w(TAG, "telephony registry not ready.");
+            }
         } catch (RemoteException ex) {
             // system process dead
-        } catch (NullPointerException ex) {
-            // system process dead
         }
     }
 
diff --git a/android/telephony/mbms/DownloadStateCallback.java b/android/telephony/mbms/DownloadStateCallback.java
index 86920bd..892fbf0 100644
--- a/android/telephony/mbms/DownloadStateCallback.java
+++ b/android/telephony/mbms/DownloadStateCallback.java
@@ -16,8 +16,12 @@
 
 package android.telephony.mbms;
 
+import android.annotation.IntDef;
 import android.telephony.MbmsDownloadSession;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A optional listener class used by download clients to track progress. Apps should extend this
  * class and pass an instance into
@@ -29,6 +33,71 @@
 public class DownloadStateCallback {
 
     /**
+     * Bitmask flags used for filtering out callback methods. Used when constructing the
+     * DownloadStateCallback as an optional parameter.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+    public @interface FilterFlag {}
+
+    /**
+     * Receive all callbacks.
+     * Default value.
+     */
+    public static final int ALL_UPDATES = 0x00;
+    /**
+     * Receive callbacks for {@link #onProgressUpdated}.
+     */
+    public static final int PROGRESS_UPDATES = 0x01;
+    /**
+     * Receive callbacks for {@link #onStateUpdated}.
+     */
+    public static final int STATE_UPDATES = 0x02;
+
+    private final int mCallbackFilterFlags;
+
+    /**
+     * Creates a DownloadStateCallback that will receive all callbacks.
+     */
+    public DownloadStateCallback() {
+        mCallbackFilterFlags = ALL_UPDATES;
+    }
+
+    /**
+     * Creates a DownloadStateCallback that will only receive callbacks for the methods specified
+     * via the filterFlags parameter.
+     * @param filterFlags A bitmask of filter flags that will specify which callback this instance
+     *     is interested in.
+     */
+    public DownloadStateCallback(int filterFlags) {
+        mCallbackFilterFlags = filterFlags;
+    }
+
+    /**
+     * Return the currently set filter flags.
+     * @return An integer containing the bitmask of flags that this instance is interested in.
+     * @hide
+     */
+    public int getCallbackFilterFlags() {
+        return mCallbackFilterFlags;
+    }
+
+    /**
+     * Returns true if a filter flag is set for a particular callback method. If the flag is set,
+     * the callback will be delivered to the listening process.
+     * @param flag A filter flag specifying whether or not a callback method is registered to
+     *     receive callbacks.
+     * @return true if registered to receive callbacks in the listening process, false if not.
+     */
+    public final boolean isFilterFlagSet(@FilterFlag int flag) {
+        if (mCallbackFilterFlags == ALL_UPDATES) {
+            return true;
+        }
+        return (mCallbackFilterFlags & flag) > 0;
+    }
+
+    /**
      * Called when the middleware wants to report progress for a file in a {@link DownloadRequest}.
      *
      * @param request a {@link DownloadRequest}, indicating which download is being referenced.
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index 61415b5..fe27537 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,6 +165,12 @@
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
                 return false;
             }
+            // We do not need to verify below extras if the result is not success.
+            if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
+                    intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+                    MbmsDownloadSession.RESULT_CANCELLED)) {
+                return true;
+            }
             if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
                 Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
                 return false;
diff --git a/android/telephony/mbms/ServiceInfo.java b/android/telephony/mbms/ServiceInfo.java
index 9a01ed0..8529f52 100644
--- a/android/telephony/mbms/ServiceInfo.java
+++ b/android/telephony/mbms/ServiceInfo.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -62,12 +63,6 @@
             throw new RuntimeException("bad locales length " + newLocales.size());
         }
 
-        for (Locale l : newLocales) {
-            if (!newNames.containsKey(l)) {
-                throw new IllegalArgumentException("A name must be provided for each locale");
-            }
-        }
-
         names = new HashMap(newNames.size());
         names.putAll(newNames);
         className = newClassName;
@@ -127,7 +122,7 @@
      * Get the user-displayable name for this cell-broadcast service corresponding to the
      * provided {@link Locale}.
      * @param locale The {@link Locale} in which you want the name of the service. This must be a
-     *               value from the list returned by {@link #getLocales()} -- an
+     *               value from the set returned by {@link #getNamedContentLocales()} -- an
      *               {@link java.util.NoSuchElementException} may be thrown otherwise.
      * @return The {@link CharSequence} providing the name of the service in the given
      *         {@link Locale}
@@ -140,6 +135,17 @@
     }
 
     /**
+     * Return an unmodifiable set of the current {@link Locale}s that have a user-displayable name
+     * associated with them. The user-displayable name associated with any {@link Locale} in this
+     * set can be retrieved with {@link #getNameForLocale(Locale)}.
+     * @return An unmodifiable set of {@link Locale} objects corresponding to a user-displayable
+     * content name in that locale.
+     */
+    public @NonNull Set<Locale> getNamedContentLocales() {
+        return Collections.unmodifiableSet(names.keySet());
+    }
+
+    /**
      * The class name for this service - used to categorize and filter
      */
     public String getServiceClassName() {
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index d845a57..2f85a1d 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -46,6 +46,47 @@
     private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
     private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
 
+
+    // Filters the DownloadStateCallbacks by its configuration from the app.
+    private abstract static class FilteredDownloadStateCallback extends DownloadStateCallback {
+
+        private final IDownloadStateCallback mCallback;
+        public FilteredDownloadStateCallback(IDownloadStateCallback callback, int callbackFlags) {
+            super(callbackFlags);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
+                int currentDownloadSize, int fullDownloadSize, int currentDecodedSize,
+                int fullDecodedSize) {
+            if (!isFilterFlagSet(PROGRESS_UPDATES)) {
+                return;
+            }
+            try {
+                mCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+                        fullDownloadSize, currentDecodedSize, fullDecodedSize);
+            } catch (RemoteException e) {
+                onRemoteException(e);
+            }
+        }
+
+        @Override
+        public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+                @MbmsDownloadSession.DownloadStatus int state) {
+            if (!isFilterFlagSet(STATE_UPDATES)) {
+                return;
+            }
+            try {
+                mCallback.onStateUpdated(request, fileInfo, state);
+            } catch (RemoteException e) {
+                onRemoteException(e);
+            }
+        }
+
+        protected abstract void onRemoteException(RemoteException e);
+    }
+
     /**
      * Initialize the download service for this app and subId, registering the listener.
      *
@@ -196,9 +237,8 @@
      * @hide
      */
     @Override
-    public final int registerStateCallback(
-            final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
-            throws RemoteException {
+    public final int registerStateCallback(final DownloadRequest downloadRequest,
+            final IDownloadStateCallback callback, int flags) throws RemoteException {
         final int uid = Binder.getCallingUid();
         DeathRecipient deathRecipient = new DeathRecipient() {
             @Override
@@ -211,28 +251,10 @@
         mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
         callback.asBinder().linkToDeath(deathRecipient, 0);
 
-        DownloadStateCallback exposedCallback = new DownloadStateCallback() {
+        DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
             @Override
-            public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
-                    currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
-                    fullDecodedSize) {
-                try {
-                    callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
-                            fullDownloadSize,
-                            currentDecodedSize, fullDecodedSize);
-                } catch (RemoteException e) {
-                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                }
-            }
-
-            @Override
-            public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
-                    @MbmsDownloadSession.DownloadStatus int state) {
-                try {
-                    callback.onStateUpdated(request, fileInfo, state);
-                } catch (RemoteException e) {
-                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                }
+            protected void onRemoteException(RemoteException e) {
+                onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
             }
         };
 
diff --git a/android/telephony/mbms/vendor/VendorUtils.java b/android/telephony/mbms/vendor/VendorUtils.java
index 8fb27b2..a43f122 100644
--- a/android/telephony/mbms/vendor/VendorUtils.java
+++ b/android/telephony/mbms/vendor/VendorUtils.java
@@ -38,8 +38,9 @@
 
     /**
      * The MBMS middleware should send this when a download of single file has completed or
-     * failed. Mandatory extras are
+     * failed. The only mandatory extra is
      * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+     * and the following are required when the download has completed:
      * {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
      * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
      * {@link #EXTRA_TEMP_LIST}
diff --git a/android/text/BoringLayoutCreateDrawPerfTest.java b/android/text/BoringLayoutCreateDrawPerfTest.java
new file mode 100644
index 0000000..47dd257
--- /dev/null
+++ b/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+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.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for {@link BoringLayout} create and draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class BoringLayoutCreateDrawPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+    private static final float SPACING_ADD = 10f;
+    private static final float SPACING_MULT = 1.5f;
+
+    @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                for (TextType textType : new TextType[]{TextType.STRING,
+                        TextType.SPANNABLE_BUILDER}) {
+                    params.add(new Object[]{textType.name(), length, textType, cached});
+                }
+            }
+        }
+        return params;
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public BoringLayoutCreateDrawPerfTest(String label, int length, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mCached = cached;
+        mTextType = textType;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+    }
+
+    /**
+     * Measures the creation time for {@link BoringLayout}.
+     */
+    @Test
+    public void timeCreate() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText();
+        // isBoring result is calculated in another test, we want to measure only the
+        // create time for Boring without isBoring check. Therefore it is calculated here.
+        final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint);
+        if (mCached) createLayout(text, metrics);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            createLayout(text, metrics);
+        }
+    }
+
+    /**
+     * Measures the draw time for {@link BoringLayout} or {@link StaticLayout}.
+     */
+    @Test
+    public void timeDraw() throws Throwable {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final CharSequence text = createRandomText();
+        final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint);
+        final Layout layout = createLayout(text, metrics);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            final int save = canvas.save();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            layout.draw(canvas);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private CharSequence createRandomText() {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(mLength)
+                .setCreateBoring(true)
+                .setTextType(mTextType)
+                .build();
+    }
+
+    private Layout createLayout(CharSequence text,
+            BoringLayout.Metrics metrics) {
+        return BoringLayout.make(text, mTextPaint, Integer.MAX_VALUE /*width*/,
+                ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, metrics, true /*includePad*/);
+    }
+}
diff --git a/android/text/BoringLayoutIsBoringPerfTest.java b/android/text/BoringLayoutIsBoringPerfTest.java
new file mode 100644
index 0000000..34de65d
--- /dev/null
+++ b/android/text/BoringLayoutIsBoringPerfTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.text;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+
+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.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for {@link BoringLayout#isBoring(CharSequence, TextPaint)}.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class BoringLayoutIsBoringPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    @Parameterized.Parameters(name = "cached={4},{1} chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean boring : BOOLEANS) {
+                for (boolean cached : BOOLEANS) {
+                    for (TextType textType : new TextType[]{TextType.STRING,
+                            TextType.SPANNABLE_BUILDER}) {
+                        params.add(new Object[]{
+                                (boring ? "Boring" : "NotBoring") + "," + textType.name(),
+                                length, boring, textType, cached});
+                    }
+                }
+            }
+        }
+        return params;
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCreateBoring;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public BoringLayoutIsBoringPerfTest(String label, int length, boolean boring, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mCreateBoring = boring;
+        mCached = cached;
+        mTextType = textType;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+    }
+
+    /**
+     * Measure the time for the {@link BoringLayout#isBoring(CharSequence, TextPaint)}.
+     */
+    @Test
+    public void timeIsBoring() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText();
+        if (mCached) BoringLayout.isBoring(text, mTextPaint);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            BoringLayout.isBoring(text, mTextPaint);
+        }
+    }
+
+    private CharSequence createRandomText() {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(mLength)
+                .setCreateBoring(mCreateBoring)
+                .setTextType(mTextType)
+                .build();
+    }
+}
diff --git a/android/text/DynamicLayout.java b/android/text/DynamicLayout.java
index 5e40935..24260c4 100644
--- a/android/text/DynamicLayout.java
+++ b/android/text/DynamicLayout.java
@@ -384,7 +384,7 @@
 
     private DynamicLayout(@NonNull Builder b) {
         super(createEllipsizer(b.mEllipsize, b.mDisplay),
-                b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
+                b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
 
         mDisplay = b.mDisplay;
         mIncludePad = b.mIncludePad;
diff --git a/android/text/DynamicLayoutPerfTest.java b/android/text/DynamicLayoutPerfTest.java
index e644a1f..b4c7f54 100644
--- a/android/text/DynamicLayoutPerfTest.java
+++ b/android/text/DynamicLayoutPerfTest.java
@@ -16,35 +16,28 @@
 
 package android.text;
 
-import android.app.Activity;
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
-import android.os.Bundle;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
-
-import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
 import android.text.style.ReplacementSpan;
 import android.util.ArraySet;
 
-import static android.text.Layout.Alignment.ALIGN_NORMAL;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.Random;
-
-import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/android/text/Hyphenator.java b/android/text/Hyphenator.java
index ea1100e..ad26f23 100644
--- a/android/text/Hyphenator.java
+++ b/android/text/Hyphenator.java
@@ -16,7 +16,12 @@
 
 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;
@@ -24,9 +29,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
 import java.util.HashMap;
 import java.util.Locale;
 
@@ -37,39 +39,19 @@
  * @hide
  */
 public class Hyphenator {
-    // This class has deliberately simple lifetime management (no finalizer) because in
-    // the common case a process will use a very small number of locales.
-
     private static String TAG = "Hyphenator";
 
-    // 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;
-
     private final static Object sLock = new Object();
 
     @GuardedBy("sLock")
     final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
 
-    // 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;
-    final static Hyphenator sEmptyHyphenator =
-            new Hyphenator(StaticLayout.nLoadHyphenator(
-                                   null, 0, DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX),
-                           null);
+    private final long mNativePtr;
+    private final HyphenationData mData;
 
-    final private long mNativePtr;
-
-    // We retain a reference to the buffer to keep the memory mapping valid
-    @SuppressWarnings("unused")
-    final private ByteBuffer mBuffer;
-
-    private Hyphenator(long nativePtr, ByteBuffer b) {
+    private Hyphenator(long nativePtr, HyphenationData data) {
         mNativePtr = nativePtr;
-        mBuffer = b;
+        mData = data;
     }
 
     public long getNativePtr() {
@@ -90,8 +72,7 @@
                         new Locale(locale.getLanguage(), "", variant);
                 result = sMap.get(languageAndVariantOnlyLocale);
                 if (result != null) {
-                    sMap.put(locale, result);
-                    return result;
+                    return putAlias(locale, result);
                 }
             }
 
@@ -99,8 +80,7 @@
             final Locale languageOnlyLocale = new Locale(locale.getLanguage());
             result = sMap.get(languageOnlyLocale);
             if (result != null) {
-                sMap.put(locale, result);
-                return result;
+                return putAlias(locale, result);
             }
 
             // Fall back to script-only, if available
@@ -112,135 +92,94 @@
                         .build();
                 result = sMap.get(scriptOnlyLocale);
                 if (result != null) {
-                    sMap.put(locale, result);
-                    return result;
+                    return putAlias(locale, result);
                 }
             }
 
-            sMap.put(locale, sEmptyHyphenator);  // To remember we found nothing.
+            return putEmptyAlias(locale);
         }
-        return sEmptyHyphenator;
     }
 
     private static class HyphenationData {
-        final String mLanguageTag;
-        final int mMinPrefix, mMinSuffix;
+        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) {
-            this.mLanguageTag = languageTag;
-            this.mMinPrefix = minPrefix;
-            this.mMinSuffix = minSuffix;
-        }
-    }
+            mMinPrefix = minPrefix;
+            mMinSuffix = minSuffix;
 
-    private static Hyphenator loadHyphenator(HyphenationData data) {
-        String patternFilename = "hyph-" + data.mLanguageTag.toLowerCase(Locale.US) + ".hyb";
-        File patternFile = new File(getSystemHyphenatorLocation(), patternFilename);
-        if (!patternFile.canRead()) {
-            Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable");
-            return null;
-        }
-        try {
-            RandomAccessFile f = new RandomAccessFile(patternFile, "r");
-            try {
-                FileChannel fc = f.getChannel();
-                MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
-                long nativePtr = StaticLayout.nLoadHyphenator(
-                        buf, 0, data.mMinPrefix, data.mMinSuffix);
-                return new Hyphenator(nativePtr, buf);
-            } finally {
-                f.close();
+            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;
             }
-        } catch (IOException e) {
-            Log.e(TAG, "error loading hyphenation " + patternFile, e);
-            return null;
         }
     }
 
-    private static File getSystemHyphenatorLocation() {
-        return new File("/system/usr/hyphen-data");
+    // 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;
     }
 
-    // This array holds pairs of language tags that are used to prefill the map from locale to
-    // hyphenation data: The hyphenation data for the first field will be prefilled from the
-    // hyphenation data for the second field.
-    //
-    // The aliases that are computable by the get() method above are not included.
-    private static final String[][] LOCALE_FALLBACK_DATA = {
-        // 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.
-        {"en-AS", "en-US"}, // English (American Samoa)
-        {"en-GU", "en-US"}, // English (Guam)
-        {"en-MH", "en-US"}, // English (Marshall Islands)
-        {"en-MP", "en-US"}, // English (Northern Mariana Islands)
-        {"en-PR", "en-US"}, // English (Puerto Rico)
-        {"en-UM", "en-US"}, // English (United States Minor Outlying Islands)
-        {"en-VI", "en-US"}, // English (Virgin Islands)
+    // 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);
+    }
 
-        // All English locales other than those falling back to en-US are mapped to en-GB.
-        {"en", "en-GB"},
+    // 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)));
+    }
 
-        // For German, we're assuming the 1996 (and later) orthography by default.
-        {"de", "de-1996"},
-        // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
-        {"de-LI-1901", "de-CH-1901"},
+    // Caller must acquire sLock before calling this method.
+    private static Hyphenator putAlias(Locale locale, Hyphenator base) {
+        return putNewHyphenator(locale, base.mData);
+    }
 
-        // Norwegian is very probably Norwegian Bokmål.
-        {"no", "nb"},
+    // Caller must acquire sLock before calling this method.
+    private static Hyphenator putEmptyAlias(Locale locale) {
+        return putNewHyphenator(locale, HyphenationData.sEmptyData);
+    }
 
-        // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
-        {"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().
-        {"am", "und-Ethi"}, // Amharic
-        {"byn", "und-Ethi"}, // Blin
-        {"gez", "und-Ethi"}, // Geʻez
-        {"ti", "und-Ethi"}, // Tigrinya
-        {"wal", "und-Ethi"}, // Wolaytta
-    };
-
-    private static final HyphenationData[] AVAILABLE_LANGUAGES = {
-        new HyphenationData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Assamese
-        new HyphenationData("bg", 2, 2), // Bulgarian
-        new HyphenationData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Bengali
-        new HyphenationData("cu", 1, 2), // Church Slavonic
-        new HyphenationData("cy", 2, 3), // Welsh
-        new HyphenationData("da", 2, 2), // Danish
-        new HyphenationData("de-1901", 2, 2), // German 1901 orthography
-        new HyphenationData("de-1996", 2, 2), // German 1996 orthography
-        new HyphenationData("de-CH-1901", 2, 2), // Swiss High German 1901 orthography
-        new HyphenationData("en-GB", 2, 3), // British English
-        new HyphenationData("en-US", 2, 3), // American English
-        new HyphenationData("es", 2, 2), // Spanish
-        new HyphenationData("et", 2, 3), // Estonian
-        new HyphenationData("eu", 2, 2), // Basque
-        new HyphenationData("fr", 2, 3), // French
-        new HyphenationData("ga", 2, 3), // Irish
-        new HyphenationData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Gujarati
-        new HyphenationData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Hindi
-        new HyphenationData("hr", 2, 2), // Croatian
-        new HyphenationData("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.
-        new HyphenationData("hy", 2, 2), // Armenian
-        new HyphenationData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Kannada
-        new HyphenationData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Malayalam
-        new HyphenationData("mn-Cyrl", 2, 2), // Mongolian in Cyrillic script
-        new HyphenationData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Marathi
-        new HyphenationData("nb", 2, 2), // Norwegian Bokmål
-        new HyphenationData("nn", 2, 2), // Norwegian Nynorsk
-        new HyphenationData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Oriya
-        new HyphenationData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Punjabi
-        new HyphenationData("pt", 2, 3), // Portuguese
-        new HyphenationData("sl", 2, 2), // Slovenian
-        new HyphenationData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Tamil
-        new HyphenationData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Telugu
-        new HyphenationData("tk", 2, 2), // Turkmen
-        new HyphenationData("und-Ethi", 1, 1), // Any language in Ethiopic script
-    };
+    // 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
@@ -250,20 +189,85 @@
      * @hide
      */
     public static void init() {
-        sMap.put(null, null);
+        synchronized (sLock) {
+            sMap.put(null, null);
 
-        for (int i = 0; i < AVAILABLE_LANGUAGES.length; i++) {
-            HyphenationData data = AVAILABLE_LANGUAGES[i];
-            Hyphenator h = loadHyphenator(data);
-            if (h != null) {
-                sMap.put(Locale.forLanguageTag(data.mLanguageTag), h);
-            }
-        }
+            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
 
-        for (int i = 0; i < LOCALE_FALLBACK_DATA.length; i++) {
-            String language = LOCALE_FALLBACK_DATA[i][0];
-            String fallback = LOCALE_FALLBACK_DATA[i][1];
-            sMap.put(Locale.forLanguageTag(language), sMap.get(Locale.forLanguageTag(fallback)));
+            // 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);
 }
diff --git a/android/text/Hyphenator_Delegate.java b/android/text/Hyphenator_Delegate.java
deleted file mode 100644
index 499e58a..0000000
--- a/android/text/Hyphenator_Delegate.java
+++ /dev/null
@@ -1,46 +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 android.text;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-
-/**
- * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
- * <p/>
- * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
- * by calls to methods of the same name in this delegate class.
- */
-public class Hyphenator_Delegate {
-
-    private static final DelegateManager<Hyphenator_Delegate> sDelegateManager = new
-            DelegateManager<Hyphenator_Delegate>(Hyphenator_Delegate.class);
-
-    @LayoutlibDelegate
-    /*package*/ static File getSystemHyphenatorLocation() {
-        // FIXME
-        return null;
-    }
-
-    /*package*/ @SuppressWarnings("UnusedParameters")  // TODO implement this.
-    static long loadHyphenator(ByteBuffer buffer, int offset, int minPrefix, int minSuffix) {
-        return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
-    }
-}
diff --git a/android/text/Layout.java b/android/text/Layout.java
index 25f791b..60fff73 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -1915,8 +1915,7 @@
         return margin;
     }
 
-    /* package */
-    static float measurePara(TextPaint paint, CharSequence text, int start, int end,
+    private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
             TextDirectionHeuristic textDir) {
         MeasuredText mt = MeasuredText.obtain();
         TextLine tl = TextLine.obtain();
@@ -2146,18 +2145,14 @@
      * text within the layout of a line.
      */
     public static class Directions {
-        // Directions represents directional runs within a line of text.
-        // Runs are pairs of ints listed in visual order, starting from the
-        // leading margin.  The first int of each pair is the offset from
-        // the first character of the line to the start of the run.  The
-        // second int represents both the length and level of the run.
-        // The length is in the lower bits, accessed by masking with
-        // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
-        // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
-        // To simply test for an RTL direction, test the bit using
-        // DIR_RTL_FLAG, if set then the direction is rtl.
-
         /**
+         * Directions represents directional runs within a line of text. Runs are pairs of ints
+         * listed in visual order, starting from the leading margin.  The first int of each pair is
+         * the offset from the first character of the line to the start of the run.  The second int
+         * represents both the length and level of the run. The length is in the lower bits,
+         * accessed by masking with RUN_LENGTH_MASK.  The level is in the higher bits, accessed by
+         * shifting by RUN_LEVEL_SHIFT and masking by RUN_LEVEL_MASK. To simply test for an RTL
+         * direction, test the bit using RUN_RTL_FLAG, if set then the direction is rtl.
          * @hide
          */
         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
diff --git a/android/text/MeasuredText.java b/android/text/MeasuredText.java
index ce3e282..b09ccc2 100644
--- a/android/text/MeasuredText.java
+++ b/android/text/MeasuredText.java
@@ -90,10 +90,6 @@
         }
     }
 
-    void setPos(int pos) {
-        mPos = pos - mTextStart;
-    }
-
     /**
      * Analyzes text for bidirectional runs.  Allocates working buffers.
      */
@@ -160,47 +156,43 @@
         }
     }
 
+    /**
+     * Apply the style.
+     *
+     * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
+     * text width.
+     * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
+     * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+     */
     float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
         if (fm != null) {
             paint.getFontMetricsInt(fm);
         }
 
-        int p = mPos;
+        final int p = mPos;
         mPos = p + len;
 
-        // try to do widths measurement in native code, but use Java if paint has been subclassed
-        // FIXME: may want to eliminate special case for subclass
-        float[] widths = null;
-        if (mBuilder == null || paint.getClass() != TextPaint.class) {
-            widths = mWidths;
-        }
         if (mEasy) {
-            boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
-            float width = 0;
-            if (widths != null) {
-                width = paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, widths, p);
-                if (mBuilder != null) {
-                    mBuilder.addMeasuredRun(p, p + len, widths);
-                }
+            final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
+            if (mBuilder == null) {
+                return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
             } else {
-                width = mBuilder.addStyleRun(paint, p, p + len, isRtl);
+                mBuilder.addStyleRun(paint, p, p + len, isRtl);
+                return 0.0f;  // Builder.addStyleRun doesn't return the width.
             }
-            return width;
         }
 
         float totalAdvance = 0;
         int level = mLevels[p];
         for (int q = p, i = p + 1, e = p + len;; ++i) {
             if (i == e || mLevels[i] != level) {
-                boolean isRtl = (level & 0x1) != 0;
-                if (widths != null) {
+                final boolean isRtl = (level & 0x1) != 0;
+                if (mBuilder == null) {
                     totalAdvance +=
-                            paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, widths, q);
-                    if (mBuilder != null) {
-                        mBuilder.addMeasuredRun(q, i, widths);
-                    }
+                            paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                 } else {
-                    totalAdvance += mBuilder.addStyleRun(paint, q, i, isRtl);
+                    // Builder.addStyleRun doesn't return the width.
+                    mBuilder.addStyleRun(paint, q, i, isRtl);
                 }
                 if (i == e) {
                     break;
@@ -209,7 +201,7 @@
                 level = mLevels[i];
             }
         }
-        return totalAdvance;
+        return totalAdvance;  // If mBuilder is null, the result is zero.
     }
 
     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
@@ -243,7 +235,7 @@
                 for (int i = mPos + 1, e = mPos + len; i < e; i++)
                     w[i] = 0;
             } else {
-                mBuilder.addReplacementRun(mPos, mPos + len, wid);
+                mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
             }
             mPos += len;
         }
diff --git a/android/text/NonEditableTextGenerator.java b/android/text/NonEditableTextGenerator.java
new file mode 100644
index 0000000..7c0cf0e
--- /dev/null
+++ b/android/text/NonEditableTextGenerator.java
@@ -0,0 +1,138 @@
+package android.text;
+
+import static android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE;
+
+import android.text.style.BulletSpan;
+
+import java.util.Random;
+
+/**
+ *
+ */
+public class NonEditableTextGenerator {
+
+    enum TextType {
+        STRING,
+        SPANNED,
+        SPANNABLE_BUILDER
+    }
+
+    private boolean mCreateBoring;
+    private TextType mTextType;
+    private int mSequenceLength;
+    private final Random mRandom;
+
+    public NonEditableTextGenerator(Random random) {
+        mRandom = random;
+    }
+
+    public NonEditableTextGenerator setCreateBoring(boolean createBoring) {
+        mCreateBoring = createBoring;
+        return this;
+    }
+
+    public NonEditableTextGenerator setTextType(TextType textType) {
+        mTextType = textType;
+        return this;
+    }
+
+    public NonEditableTextGenerator setSequenceLength(int sequenceLength) {
+        mSequenceLength = sequenceLength;
+        return this;
+    }
+
+    /**
+     * Sample charSequence generated:
+     * NRjPzjvUadHmH ExoEoTqfx pCLw qtndsqfpk AqajVCbgjGZ igIeC dfnXRgA
+     */
+    public CharSequence build() {
+        final RandomCharSequenceGenerator sequenceGenerator = new RandomCharSequenceGenerator(
+                mRandom);
+        if (mSequenceLength > 0) {
+            sequenceGenerator.setSequenceLength(mSequenceLength);
+        }
+
+        final CharSequence charSequence = sequenceGenerator.buildLatinSequence();
+
+        switch (mTextType) {
+            case SPANNED:
+            case SPANNABLE_BUILDER:
+                return createSpannable(charSequence);
+            case STRING:
+            default:
+                return createString(charSequence);
+        }
+    }
+
+    private Spannable createSpannable(CharSequence charSequence) {
+        final Spannable spannable = (mTextType == TextType.SPANNABLE_BUILDER) ?
+                new SpannableStringBuilder(charSequence) : new SpannableString(charSequence);
+
+        if (!mCreateBoring) {
+            // add a paragraph style to make it non boring
+            spannable.setSpan(new BulletSpan(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE);
+        }
+
+        spannable.setSpan(new Object(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE);
+        spannable.setSpan(new Object(), 0, 1, SPAN_INCLUSIVE_INCLUSIVE);
+
+        return spannable;
+    }
+
+    private String createString(CharSequence charSequence) {
+        if (mCreateBoring) {
+            return charSequence.toString();
+        } else {
+            // BoringLayout checks to see if there is a surrogate pair and if so tells that
+            // the charSequence is not suitable for boring. Add an emoji to make it non boring.
+            // Emoji is added instead of RTL, since emoji stays in the same run and is a more
+            // common case.
+            return charSequence.toString() + "\uD83D\uDC68\uD83C\uDFFF";
+        }
+    }
+
+    public static class RandomCharSequenceGenerator {
+
+        private static final int DEFAULT_MIN_WORD_LENGTH = 3;
+        private static final int DEFAULT_MAX_WORD_LENGTH = 15;
+        private static final int DEFAULT_SEQUENCE_LENGTH = 256;
+
+        private int mMinWordLength = DEFAULT_MIN_WORD_LENGTH;
+        private int mMaxWordLength = DEFAULT_MAX_WORD_LENGTH;
+        private int mSequenceLength = DEFAULT_SEQUENCE_LENGTH;
+        private final Random mRandom;
+
+        public RandomCharSequenceGenerator(Random random) {
+            mRandom = random;
+        }
+
+        public RandomCharSequenceGenerator setSequenceLength(int sequenceLength) {
+            mSequenceLength = sequenceLength;
+            return this;
+        }
+
+        public CharSequence buildLatinSequence() {
+            final StringBuilder result = new StringBuilder();
+            while (result.length() < mSequenceLength) {
+                // add random word
+                result.append(buildLatinWord());
+                result.append(' ');
+            }
+            return result.substring(0, mSequenceLength);
+        }
+
+        public CharSequence buildLatinWord() {
+            final StringBuilder result = new StringBuilder();
+            // create a random length that is (mMinWordLength + random amount of chars) where
+            // total size is less than mMaxWordLength
+            final int length = mRandom.nextInt(mMaxWordLength - mMinWordLength) + mMinWordLength;
+            while (result.length() < length) {
+                // add random letter
+                int base = mRandom.nextInt(2) == 0 ? 'A' : 'a';
+                result.append(Character.toChars(mRandom.nextInt(26) + base));
+            }
+            return result.toString();
+        }
+    }
+
+}
diff --git a/android/text/PaintMeasureDrawPerfTest.java b/android/text/PaintMeasureDrawPerfTest.java
new file mode 100644
index 0000000..00b60ad
--- /dev/null
+++ b/android/text/PaintMeasureDrawPerfTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+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.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for single line measure and draw using {@link Paint} and {@link Canvas}.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class PaintMeasureDrawPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    @Parameterized.Parameters(name = "cached={1},{0} chars")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                params.add(new Object[]{length, cached});
+            }
+        }
+        return params;
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private final int mLength;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+
+    public PaintMeasureDrawPerfTest(int length, boolean cached) {
+        mLength = length;
+        mCached = cached;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+    }
+
+    /**
+     * Measure the time for {@link Paint#measureText(String)}
+     */
+    @Test
+    public void timeMeasure() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final String text = createRandomText();
+        if (mCached) mTextPaint.measureText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            mTextPaint.measureText(text);
+        }
+    }
+
+    /**
+     * Measures the time for {@link Canvas#drawText(String, float, float, Paint)}
+     */
+    @Test
+    public void timeDraw() throws Throwable {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final String text = createRandomText();
+        if (mCached) mTextPaint.measureText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            final int save = canvas.save();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            canvas.drawText(text, 0 /*x*/, 100 /*y*/, mTextPaint);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private String createRandomText() {
+        return (String) new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(mLength)
+                .setCreateBoring(true)
+                .setTextType(NonEditableTextGenerator.TextType.STRING)
+                .build();
+    }
+}
diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java
index c124c7f..961cd8e 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -28,12 +28,12 @@
 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.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -101,6 +101,7 @@
             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;
@@ -117,6 +118,9 @@
             b.mMeasuredText = null;
             b.mLeftIndents = null;
             b.mRightIndents = null;
+            b.mLocales = null;
+            b.mLeftPaddings = null;
+            b.mRightPaddings = null;
             nFinishBuilder(b.mNativePtr);
             sPool.release(b);
         }
@@ -128,6 +132,8 @@
             mPaint = null;
             mLeftIndents = null;
             mRightIndents = null;
+            mLeftPaddings = null;
+            mRightPaddings = null;
             mMeasuredText.finish();
         }
 
@@ -356,6 +362,28 @@
         }
 
         /**
+         * Set available paddings to draw overhanging text on. Arguments are arrays holding the
+         * amount of padding available, one per line, measured in pixels. For lines past the last
+         * element in the array, the last element repeats.
+         *
+         * The individual padding amounts should be non-negative. The result of passing negative
+         * paddings is undefined.
+         *
+         * @param leftPaddings array of amounts of available padding for left margin, in pixels
+         * @param rightPaddings array of amounts of available padding for right margin, in pixels
+         * @return this builder, useful for chaining
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setAvailablePaddings(@Nullable int[] leftPaddings,
+                @Nullable int[] rightPaddings) {
+            mLeftPaddings = leftPaddings;
+            mRightPaddings = rightPaddings;
+            return this;
+        }
+
+        /**
          * Set paragraph justification mode. The default value is
          * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
          * the last line will be displayed with the alignment set by {@link #setAlignment}.
@@ -401,10 +429,8 @@
          * future).
          *
          * Then, for each run within the paragraph:
-         *  - setLocales (this must be done at least for the first run, optional afterwards)
          *  - one of the following, depending on the type of run:
          *    + addStyleRun (a text run, to be measured in native code)
-         *    + addMeasuredRun (a run already measured in Java, passed into native code)
          *    + addReplacementRun (a replacement run, width is given)
          *
          * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
@@ -413,24 +439,29 @@
          * After all paragraphs, call finish() to release expensive buffers.
          */
 
-        private void setLocales(LocaleList locales) {
+        private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
+            final LocaleList locales = paint.getTextLocales();
+            final String languageTags;
+            long[] hyphenators;
             if (!locales.equals(mLocales)) {
-                nSetLocales(mNativePtr, locales.toLanguageTags(), getHyphenators(locales));
                 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 */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
-            setLocales(paint.getTextLocales());
-            return nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
+        /* 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);
         }
 
-        /* package */ void addMeasuredRun(int start, int end, float[] widths) {
-            nAddMeasuredRun(mNativePtr, start, end, widths);
-        }
-
-        /* package */ void addReplacementRun(int start, int end, float width) {
-            nAddReplacementRun(mNativePtr, start, end, width);
+        /* 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);
         }
 
         /**
@@ -478,6 +509,8 @@
         private int mHyphenationFrequency;
         @Nullable private int[] mLeftIndents;
         @Nullable private int[] mRightIndents;
+        @Nullable private int[] mLeftPaddings;
+        @Nullable private int[] mRightPaddings;
         private int mJustificationMode;
         private boolean mAddLastLineLineSpacing;
 
@@ -616,7 +649,7 @@
                 : (b.mText instanceof Spanned)
                     ? new SpannedEllipsizer(b.mText)
                     : new Ellipsizer(b.mText),
-                b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
+                b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
 
         if (b.mEllipsize != null) {
             Ellipsizer e = (Ellipsizer) getText();
@@ -638,6 +671,8 @@
 
         mLeftIndents = b.mLeftIndents;
         mRightIndents = b.mRightIndents;
+        mLeftPaddings = b.mLeftPaddings;
+        mRightPaddings = b.mRightPaddings;
         setJustificationMode(b.mJustificationMode);
 
         generate(b, b.mIncludePad, b.mIncludePad);
@@ -662,7 +697,6 @@
         // store fontMetrics per span range
         // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
         int[] fmCache = new int[4 * 4];
-        b.setLocales(paint.getTextLocales());
 
         mLineCount = 0;
         mEllipsized = false;
@@ -776,11 +810,17 @@
                 }
             }
 
+            // 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,
                     // TODO: Support more justification mode, e.g. letter spacing, stretching.
-                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLineCount);
+                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+                    // TODO: indents and paddings don't need to get passed to native code for every
+                    // paragraph. Pass them to native code just once.
+                    indents, mLeftPaddings, mRightPaddings, mLineCount);
 
             // measurement has to be done before performing line breaking
             // but we don't want to recompute fontmetrics or span ranges the
@@ -1491,28 +1531,25 @@
     private static native void nFreeBuilder(long nativePtr);
     private static native void nFinishBuilder(long nativePtr);
 
-    /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset,
-            int minPrefix, int minSuffix);
-
-    private static native void nSetLocales(long nativePtr, String locales,
-            long[] nativeHyphenators);
-
     // Set up paragraph text and settings; done as one big method to minimize jni crossings
     private static native void nSetupParagraph(
-            @NonNull long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
+            /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
             @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
             @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
             int defaultTabStop, @BreakStrategy int breakStrategy,
             @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
-            @Nullable int[] indents, @IntRange(from = 0) int indentsOffset);
+            @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
+            @IntRange(from = 0) int indentsOffset);
 
-    private static native float nAddStyleRun(long nativePtr, long nativePaint, int start, int end,
-            boolean isRtl);
+    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);
 
-    private static native void nAddMeasuredRun(long nativePtr,
-            int start, int end, float[] widths);
-
-    private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
+    private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
+            @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);
 
@@ -1590,4 +1627,6 @@
 
     @Nullable private int[] mLeftIndents;
     @Nullable private int[] mRightIndents;
+    @Nullable private int[] mLeftPaddings;
+    @Nullable private int[] mRightPaddings;
 }
diff --git a/android/text/StaticLayoutCreateDrawPerfTest.java b/android/text/StaticLayoutCreateDrawPerfTest.java
new file mode 100644
index 0000000..356e2e0
--- /dev/null
+++ b/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+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.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class StaticLayoutCreateDrawPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    private static final float SPACING_ADD = 10f;
+    private static final float SPACING_MULT = 1.5f;
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                for (TextType textType : new TextType[]{TextType.STRING,
+                        TextType.SPANNABLE_BUILDER}) {
+                    params.add(new Object[]{textType.name(), length, textType, cached});
+                }
+            }
+        }
+        return params;
+    }
+
+    private final int mLineWidth;
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public StaticLayoutCreateDrawPerfTest(String label, int length, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mTextType = textType;
+        mCached = cached;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+        mLineWidth = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Measures the creation time for a multi line {@link StaticLayout}.
+     */
+    @Test
+    public void timeCreate() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText(mLength);
+        createLayout(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            createLayout(text);
+        }
+    }
+
+    /**
+     * Measures the draw time for a multi line {@link StaticLayout}.
+     */
+    @Test
+    public void timeDraw() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final CharSequence text = createRandomText(mLength);
+        final Layout layout = createLayout(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            int save = canvas.save();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            layout.draw(canvas);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private Layout createLayout(CharSequence text) {
+        return StaticLayout.Builder.obtain(text, 0 /*start*/, text.length() /*end*/, mTextPaint,
+                mLineWidth)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(true)
+                .setLineSpacing(SPACING_ADD, SPACING_MULT)
+                .build();
+    }
+
+    private CharSequence createRandomText(int length) {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(length)
+                .setTextType(mTextType)
+                .build();
+    }
+}
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index 0d58bcc..63337f0 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -53,26 +53,11 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset, int minPrefix,
-            int minSuffix) {
-        return Hyphenator_Delegate.loadHyphenator(buf, offset, minPrefix, minSuffix);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nSetLocales(long nativeBuilder, String locales,
-            long[] nativeHyphenators) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder != null) {
-            builder.mLocales = locales;
-            builder.mNativeHyphenators = nativeHyphenators;
-        }
-    }
-
-    @LayoutlibDelegate
     /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
             float firstWidth, int firstWidthLineCount, float restWidth,
             int[] variableTabStops, int defaultTabStop, int breakStrategy,
-            int hyphenationFrequency, boolean isJustified, int[] indents, int intentsOffset) {
+            int hyphenationFrequency, boolean isJustified, int[] indents, int[] leftPaddings,
+            int[] rightPaddings, int intentsOffset) {
         // TODO: implement justified alignment
         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
         if (builder == null) {
@@ -86,30 +71,29 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, int start,
-            int end, boolean isRtl) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-
-        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
-        return builder == null ? 0 :
-                measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
-                        bidiFlags);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder != null) {
-            System.arraycopy(widths, start, builder.mWidths, start, end - start);
-        }
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
+    /*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start,
+            int end, boolean isRtl, String languageTags, long[] hyphenators) {
         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,
+                bidiFlags);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width,
+            String languageTags, long[] hyphenators) {
+        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);
     }
diff --git a/android/text/TextViewSetTextMeasurePerfTest.java b/android/text/TextViewSetTextMeasurePerfTest.java
new file mode 100644
index 0000000..a2bf33e
--- /dev/null
+++ b/android/text/TextViewSetTextMeasurePerfTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.text;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.widget.TextView;
+
+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.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+
+/**
+ * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class TextViewSetTextMeasurePerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                for (TextType textType : new TextType[]{TextType.STRING,
+                        TextType.SPANNABLE_BUILDER}) {
+                    params.add(new Object[]{textType.name(), length, textType, cached});
+                }
+            }
+        }
+        return params;
+    }
+
+    private final int mLineWidth;
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public TextViewSetTextMeasurePerfTest(String label, int length, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mTextType = textType;
+        mCached = cached;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+        mLineWidth = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Measures the time to setText and measure for a {@link TextView}.
+     */
+    @Test
+    public void timeCreate() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText(mLength);
+        final TextView textView = new TextView(InstrumentationRegistry.getTargetContext());
+        textView.setText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            textView.setTextLocale(Locale.UK);
+            textView.setTextLocale(Locale.US);
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            textView.setText(text);
+            textView.measure(AT_MOST | mLineWidth, UNSPECIFIED);
+        }
+    }
+
+    /**
+     * Measures the time to draw for a {@link TextView}.
+     */
+    @Test
+    public void timeDraw() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final CharSequence text = createRandomText(mLength);
+        final TextView textView = new TextView(InstrumentationRegistry.getTargetContext());
+        textView.setText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            int save = canvas.save();
+            textView.setTextLocale(Locale.UK);
+            textView.setTextLocale(Locale.US);
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            textView.draw(canvas);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private CharSequence createRandomText(int length) {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(length)
+                .setCreateBoring(false)
+                .setTextType(mTextType)
+                .build();
+    }
+}
diff --git a/android/text/format/Formatter.java b/android/text/format/Formatter.java
index fc56455..2c83fc4 100644
--- a/android/text/format/Formatter.java
+++ b/android/text/format/Formatter.java
@@ -32,6 +32,7 @@
 import android.text.TextUtils;
 import android.view.View;
 
+import java.lang.reflect.Constructor;
 import java.math.BigDecimal;
 import java.util.Locale;
 
@@ -194,13 +195,29 @@
 
     /**
      * ICU doesn't support PETABYTE yet. Fake it so that we can treat all units the same way.
-     * {@hide}
      */
-    public static final MeasureUnit PETABYTE = MeasureUnit.internalGetInstance(
-            "digital", "petabyte");
+    private static final MeasureUnit PETABYTE = createPetaByte();
 
-    /** {@hide} */
-    public static class RoundedBytesResult {
+    /**
+     * Create a petabyte MeasureUnit without registering it with ICU.
+     * ICU doesn't support user-create MeasureUnit and the only public (but hidden) method to do so
+     * is {@link MeasureUnit#internalGetInstance(String, String)} which also registers the unit as
+     * an available type and thus leaks it to code that doesn't expect or support it.
+     * <p>This method uses reflection to create an instance of MeasureUnit to avoid leaking it. This
+     * instance is <b>only</b> to be used in this class.
+     */
+    private static MeasureUnit createPetaByte() {
+        try {
+            Constructor<MeasureUnit> constructor = MeasureUnit.class
+                    .getDeclaredConstructor(String.class, String.class);
+            constructor.setAccessible(true);
+            return constructor.newInstance("digital", "petabyte");
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException("Failed to create petabyte MeasureUnit", e);
+        }
+    }
+
+    private static class RoundedBytesResult {
         public final float value;
         public final MeasureUnit units;
         public final int fractionDigits;
@@ -218,7 +235,7 @@
          * Returns a RoundedBytesResult object based on the input size in bytes and the rounding
          * flags. The result can be used for formatting.
          */
-        public static RoundedBytesResult roundBytes(long sizeBytes, int flags) {
+        static RoundedBytesResult roundBytes(long sizeBytes, int flags) {
             final boolean isNegative = (sizeBytes < 0);
             float result = isNegative ? -sizeBytes : sizeBytes;
             MeasureUnit units = MeasureUnit.BYTE;
diff --git a/android/transition/TransitionUtils.java b/android/transition/TransitionUtils.java
index 4951237..084b79d 100644
--- a/android/transition/TransitionUtils.java
+++ b/android/transition/TransitionUtils.java
@@ -101,7 +101,7 @@
 
         ImageView copy = new ImageView(view.getContext());
         copy.setScaleType(ImageView.ScaleType.CENTER_CROP);
-        Bitmap bitmap = createViewBitmap(view, matrix, bounds);
+        Bitmap bitmap = createViewBitmap(view, matrix, bounds, sceneRoot);
         if (bitmap != null) {
             copy.setImageBitmap(bitmap);
         }
@@ -115,7 +115,7 @@
     /**
      * Get a copy of bitmap of given drawable, return null if intrinsic size is zero
      */
-    public static Bitmap createDrawableBitmap(Drawable drawable) {
+    public static Bitmap createDrawableBitmap(Drawable drawable, View hostView) {
         int width = drawable.getIntrinsicWidth();
         int height = drawable.getIntrinsicHeight();
         if (width <= 0 || height <= 0) {
@@ -128,7 +128,7 @@
         }
         int bitmapWidth = (int) (width * scale);
         int bitmapHeight = (int) (height * scale);
-        final RenderNode node = RenderNode.create("TransitionUtils", null);
+        final RenderNode node = RenderNode.create("TransitionUtils", hostView);
         node.setLeftTopRightBottom(0, 0, width, height);
         node.setClipToBounds(false);
         final DisplayListCanvas canvas = node.start(width, height);
@@ -156,20 +156,30 @@
      *               returning.
      * @param bounds The bounds of the bitmap in the destination coordinate system (where the
      *               view should be presented. Typically, this is matrix.mapRect(viewBounds);
+     * @param sceneRoot A ViewGroup that is attached to the window to temporarily contain the view
+     *                  if it isn't attached to the window.
      * @return A bitmap of the given view or null if bounds has no width or height.
      */
-    public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds) {
+    public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds,
+            ViewGroup sceneRoot) {
+        final boolean addToOverlay = !view.isAttachedToWindow();
+        if (addToOverlay) {
+            if (sceneRoot == null || !sceneRoot.isAttachedToWindow()) {
+                return null;
+            }
+            sceneRoot.getOverlay().add(view);
+        }
         Bitmap bitmap = null;
         int bitmapWidth = Math.round(bounds.width());
         int bitmapHeight = Math.round(bounds.height());
         if (bitmapWidth > 0 && bitmapHeight > 0) {
-            float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
+            float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
             bitmapWidth *= scale;
             bitmapHeight *= scale;
             matrix.postTranslate(-bounds.left, -bounds.top);
             matrix.postScale(scale, scale);
 
-            final RenderNode node = RenderNode.create("TransitionUtils", null);
+            final RenderNode node = RenderNode.create("TransitionUtils", view);
             node.setLeftTopRightBottom(0, 0, bitmapWidth, bitmapHeight);
             node.setClipToBounds(false);
             final DisplayListCanvas canvas = node.start(bitmapWidth, bitmapHeight);
@@ -178,6 +188,9 @@
             node.end(canvas);
             bitmap = ThreadedRenderer.createHardwareBitmap(node, bitmapWidth, bitmapHeight);
         }
+        if (addToOverlay) {
+            sceneRoot.getOverlay().remove(view);
+        }
         return bitmap;
     }
 
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java
index 5838f95..fc1d487 100644
--- a/android/util/FeatureFlagUtils.java
+++ b/android/util/FeatureFlagUtils.java
@@ -50,6 +50,13 @@
     }
 
     /**
+     * Override feature flag to new state.
+     */
+    public static void setEnabled(String feature, boolean enabled) {
+        SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
+    }
+
+    /**
      * Returns all feature flags in their raw form.
      */
     public static Map<String, String> getAllFeatureFlags() {
diff --git a/android/util/Log.java b/android/util/Log.java
index 8691136..0299865 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -392,7 +392,7 @@
         // 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 = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD  // Base.
+        int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD    // Base.
                 - 2                                                // Two terminators.
                 - (tag != null ? tag.length() : 0)                 // Tag length.
                 - 32;                                              // Some slack.
@@ -429,10 +429,10 @@
     }
 
     /**
-     * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
+     * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
      * a JNI call during logging.
      */
-    static class NoPreloadHolder {
+    static class PreloadHolder {
         public final static int LOGGER_ENTRY_MAX_PAYLOAD =
                 logger_entry_max_payload_native();
     }
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 5208606..4015488 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,10 +20,6 @@
 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
@@ -91,9 +87,8 @@
 
     /**
      * Sets the size of the cache.
-     * @param maxSize The new maximum size.
      *
-     * @hide
+     * @param maxSize The new maximum size.
      */
     public void resize(int maxSize) {
         if (maxSize <= 0) {
@@ -190,10 +185,13 @@
     }
 
     /**
+     * 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.
      */
-    private void trimToSize(int maxSize) {
+    public void trimToSize(int maxSize) {
         while (true) {
             K key;
             V value;
@@ -207,16 +205,7 @@
                     break;
                 }
 
-                // 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
-
+                Map.Entry<K, V> toEvict = map.eldest();
                 if (toEvict == null) {
                     break;
                 }
diff --git a/android/util/StatsLogKey.java b/android/util/StatsLogKey.java
new file mode 100644
index 0000000..9ad0a23
--- /dev/null
+++ b/android/util/StatsLogKey.java
@@ -0,0 +1,48 @@
+/*
+ * 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
new file mode 100644
index 0000000..5e5a828
--- /dev/null
+++ b/android/util/StatsLogTag.java
@@ -0,0 +1,32 @@
+/*
+ * 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
new file mode 100644
index 0000000..05b9d93
--- /dev/null
+++ b/android/util/StatsLogValue.java
@@ -0,0 +1,54 @@
+/*
+ * 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/proto/ProtoOutputStream.java b/android/util/proto/ProtoOutputStream.java
index 9afa56d..43a9789 100644
--- a/android/util/proto/ProtoOutputStream.java
+++ b/android/util/proto/ProtoOutputStream.java
@@ -29,8 +29,8 @@
  * Class to write to a protobuf stream.
  *
  * Each write method takes an ID code from the protoc generated classes
- * and the value to write.  To make a nested object, call startObject
- * and then endObject when you are done.
+ * and the value to write.  To make a nested object, call #start
+ * and then #end when you are done.
  *
  * The ID codes have type information embedded into them, so if you call
  * the incorrect function you will get an IllegalArgumentException.
@@ -60,16 +60,16 @@
  * Message objects. We need to find another way.
  *
  * So what we do here is to let the calling code write the data into a
- * byte[] (actually a collection of them wrapped in the EncodedBuffer) class,
+ * byte[] (actually a collection of them wrapped in the EncodedBuffer class),
  * but not do the varint encoding of the sub-message sizes.  Then, we do a
  * recursive traversal of the buffer itself, calculating the sizes (which are
  * then knowable, although still not the actual sizes in the buffer because of
  * possible further nesting).  Then we do a third pass, compacting the
  * buffer and varint encoding the sizes.
  *
- * This gets us a relatively small number number of fixed-size allocations,
+ * This gets us a relatively small number of fixed-size allocations,
  * which is less likely to cause memory fragmentation or churn the GC, and
- * the same number of data copies as would have gotten with setting it
+ * the same number of data copies as we would have gotten with setting it
  * field-by-field in generated code, and no code bloat from generated code.
  * The final data copy is also done with System.arraycopy, which will be
  * more efficient, in general, than doing the individual fields twice (as in
@@ -77,26 +77,26 @@
  *
  * To accomplish the multiple passes, whenever we write a
  * WIRE_TYPE_LENGTH_DELIMITED field, we write the size occupied in our
- * buffer as a fixed 32 bit int (called childRawSize), not variable length
+ * buffer as a fixed 32 bit int (called childRawSize), not a variable length
  * one. We reserve another 32 bit slot for the computed size (called
  * childEncodedSize).  If we know the size up front, as we do for strings
  * and byte[], then we also put that into childEncodedSize, if we don't, we
- * write the negative of childRawSize, as a sentiel that we need to
+ * write the negative of childRawSize, as a sentinel that we need to
  * compute it during the second pass and recursively compact it during the
  * third pass.
  *
- * Unsgigned size varints can be up to five bytes long, but we reserve eight
+ * Unsigned size varints can be up to five bytes long, but we reserve eight
  * bytes for overhead, so we know that when we compact the buffer, there
  * will always be space for the encoded varint.
  *
  * When we can figure out the size ahead of time, we do, in order
  * to save overhead with recalculating it, and with the later arraycopy.
  *
- * During the period between when the caller has called startObject, but
- * not yet called endObject, we maintain a linked list of the tokens
- * returned by startObject, stored in those 8 bytes of size storage space.
+ * During the period between when the caller has called #start, but
+ * not yet called #end, we maintain a linked list of the tokens
+ * returned by #start, stored in those 8 bytes of size storage space.
  * We use that linked list of tokens to ensure that the caller has
- * correctly matched pairs of startObject and endObject calls, and issue
+ * correctly matched pairs of #start and #end calls, and issue
  * errors if they are not matched.
  */
 @TestApi
@@ -2375,6 +2375,9 @@
         if (countString == null) {
             countString = "fieldCount=" + fieldCount;
         }
+        if (countString.length() > 0) {
+            countString += " ";
+        }
 
         final long fieldType = fieldId & FIELD_TYPE_MASK;
         String typeString = getFieldTypeString(fieldType);
@@ -2382,7 +2385,7 @@
             typeString = "fieldType=" + fieldType;
         }
 
-        return fieldCount + " " + typeString + " tag=" + ((int)fieldId)
+        return countString + typeString + " tag=" + ((int) fieldId)
                 + " fieldId=0x" + Long.toHexString(fieldId);
     }
 
diff --git a/android/util/proto/ProtoUtils.java b/android/util/proto/ProtoUtils.java
new file mode 100644
index 0000000..449baca
--- /dev/null
+++ b/android/util/proto/ProtoUtils.java
@@ -0,0 +1,39 @@
+/*
+ * 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.util.proto;
+
+import android.util.AggStats;
+
+/**
+ * This class contains a list of helper functions to write common proto in
+ * //frameworks/base/core/proto/android/base directory
+ */
+public class ProtoUtils {
+
+    /**
+     * Dump AggStats to ProtoOutputStream
+     * @hide
+     */
+    public static void toAggStatsProto(ProtoOutputStream proto, long fieldId,
+            long min, long average, long max) {
+        final long aggStatsToken = proto.start(fieldId);
+        proto.write(AggStats.MIN, min);
+        proto.write(AggStats.AVERAGE, average);
+        proto.write(AggStats.MAX, max);
+        proto.end(aggStatsToken);
+    }
+}
diff --git a/android/view/DragEvent.java b/android/view/DragEvent.java
index 16f2d7d..2c9f871 100644
--- a/android/view/DragEvent.java
+++ b/android/view/DragEvent.java
@@ -103,7 +103,7 @@
  *  <tr>
  *      <td>ACTION_DRAG_ENDED</td>
  *      <td style="text-align: center;">&nbsp;</td>
- *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">X</td>
  *      <td style="text-align: center;">&nbsp;</td>
  *      <td style="text-align: center;">&nbsp;</td>
  *      <td style="text-align: center;">&nbsp;</td>
@@ -112,6 +112,7 @@
  * </table>
  * <p>
  *  The {@link android.view.DragEvent#getAction()},
+ *  {@link android.view.DragEvent#getLocalState()}
  *  {@link android.view.DragEvent#describeContents()},
  *  {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and
  *  {@link android.view.DragEvent#toString()} methods always return valid data.
@@ -397,7 +398,7 @@
      * operation. In all other activities this method will return null
      * </p>
      * <p>
-     *  This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
+     *  This method returns valid data for all event actions.
      * </p>
      * @return The local state object sent to the system by startDragAndDrop().
      */
diff --git a/android/view/Gravity.java b/android/view/Gravity.java
index 324a1ae..232ff25 100644
--- a/android/view/Gravity.java
+++ b/android/view/Gravity.java
@@ -440,4 +440,57 @@
         }
         return result;
     }
+
+    /**
+     * @hide
+     */
+    public static String toString(int gravity) {
+        final StringBuilder result = new StringBuilder();
+        if ((gravity & FILL) != 0) {
+            result.append("FILL").append(' ');
+        } else {
+            if ((gravity & FILL_VERTICAL) != 0) {
+                result.append("FILL_VERTICAL").append(' ');
+            } else {
+                if ((gravity & TOP) != 0) {
+                    result.append("TOP").append(' ');
+                }
+                if ((gravity & BOTTOM) != 0) {
+                    result.append("BOTTOM").append(' ');
+                }
+            }
+            if ((gravity & FILL_HORIZONTAL) != 0) {
+                result.append("FILL_HORIZONTAL").append(' ');
+            } else {
+                if ((gravity & START) != 0) {
+                    result.append("START").append(' ');
+                } else if ((gravity & LEFT) != 0) {
+                    result.append("LEFT").append(' ');
+                }
+                if ((gravity & END) != 0) {
+                    result.append("END").append(' ');
+                } else if ((gravity & RIGHT) != 0) {
+                    result.append("RIGHT").append(' ');
+                }
+            }
+        }
+        if ((gravity & CENTER) != 0) {
+            result.append("CENTER").append(' ');
+        } else {
+            if ((gravity & CENTER_VERTICAL) != 0) {
+                result.append("CENTER_VERTICAL").append(' ');
+            }
+            if ((gravity & CENTER_HORIZONTAL) != 0) {
+                result.append("CENTER_HORIZONTAL").append(' ');
+            }
+        }
+        if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+            result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+        }
+        if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+            result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+        }
+        result.deleteCharAt(result.length() - 1);
+        return result.toString();
+    }
 }
diff --git a/android/view/MenuInflater_Delegate.java b/android/view/MenuInflater_Delegate.java
index 08a97d6..977a2a7 100644
--- a/android/view/MenuInflater_Delegate.java
+++ b/android/view/MenuInflater_Delegate.java
@@ -42,7 +42,6 @@
  * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
  */
 public class MenuInflater_Delegate {
-
     @LayoutlibDelegate
     /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
             AttributeSet attrs) {
@@ -56,10 +55,15 @@
                 return;
             }
         }
-        // This means that Bridge did not take over the instantiation of some object properly.
-        // This is most likely a bug in the LayoutLib code.
-        Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
-                "Action Bar Menu rendering may be incorrect.", null);
+
+        if (menuItem == null || !menuItem.getClass().getName().startsWith("android.support.")) {
+            // This means that Bridge did not take over the instantiation of some object properly.
+            // This is most likely a bug in the LayoutLib code.
+            // We suppress this error for AppCompat menus since we do not support them in the menu
+            // editor yet.
+            Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+                    "Action Bar Menu rendering may be incorrect.", null);
+        }
 
     }
 
diff --git a/android/view/Surface.java b/android/view/Surface.java
index 2c1f734..ddced6c 100644
--- a/android/view/Surface.java
+++ b/android/view/Surface.java
@@ -762,7 +762,7 @@
                 return "ROTATION_270";
             }
             default: {
-                throw new IllegalArgumentException("Invalid rotation: " + rotation);
+                return Integer.toString(rotation);
             }
         }
     }
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 91932dd..31daeff 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 
+import android.annotation.Size;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
@@ -65,6 +66,7 @@
     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,
             float dtdy, float dsdy);
     private static native void nativeSetFlags(long nativeObject, int flags, int mask);
@@ -105,8 +107,8 @@
             long surfaceObject, long frame);
     private static native void nativeReparentChildren(long nativeObject,
             IBinder handle);
-    private static native void nativeReparentChild(long nativeObject,
-            IBinder parentHandle, IBinder childHandle);
+    private static native void nativeReparent(long nativeObject,
+            IBinder parentHandle);
     private static native void nativeSeverChildren(long nativeObject);
     private static native void nativeSetOverrideScalingMode(long nativeObject,
             int scalingMode);
@@ -455,9 +457,9 @@
         nativeReparentChildren(mNativeObject, newParentHandle);
     }
 
-    /** Re-parents a specific child layer to a new parent */
-    public void reparentChild(IBinder newParentHandle, IBinder childHandle) {
-        nativeReparentChild(mNativeObject, newParentHandle, childHandle);
+    /** Re-parents this layer to a new parent. */
+    public void reparent(IBinder newParentHandle) {
+        nativeReparent(mNativeObject, newParentHandle);
     }
 
     public void detachChildren() {
@@ -552,6 +554,15 @@
         nativeSetAlpha(mNativeObject, 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);
+    }
+
     public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
         checkNotReleased();
         nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index ebb2af4..462dad3 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,115 +16,1208 @@
 
 package android.view;
 
-import com.android.layoutlib.bridge.MockView;
+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 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;
 
 /**
- * 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.
+ * 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
  *
- * TODO: generate automatically.
+ * <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.
  *
+ * <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 MockView {
+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 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 defStyle) {
-        super(context, attrs, defStyle);
+    public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
     }
 
     public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mRenderNode.requestPositionUpdates(this);
+
+        setWillNotDraw(true);
     }
 
-    public boolean gatherTransparentRegion(Region region) {
-      return false;
-    }
-
-    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
-    }
-
-    public void setZOrderOnTop(boolean onTop) {
-    }
-
-    public void setSecure(boolean isSecure) {
-    }
-
+    /**
+     * Return the SurfaceHolder providing access and control over this
+     * SurfaceView's underlying surface.
+     *
+     * @return SurfaceHolder The holder of the surface.
+     */
     public SurfaceHolder getHolder() {
         return mSurfaceHolder;
     }
 
-    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+    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";
 
         @Override
         public boolean isCreating() {
-            return false;
+            return mIsCreating;
         }
 
         @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
-        public void setType(int type) {
-        }
+        @Deprecated
+        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 null;
+            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 lockCanvas(Rect dirty) {
+        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 void unlockCanvasAndPost(Canvas canvas) {
+            mSurface.unlockCanvasAndPost(canvas);
+            mSurfaceLock.unlock();
         }
 
         @Override
         public Surface getSurface() {
-            return null;
+            return mSurface;
         }
 
         @Override
         public Rect getSurfaceFrame() {
-            return null;
+            return mSurfaceFrame;
         }
     };
-}
 
+    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 e5bd5ac..b6be296 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -127,6 +127,7 @@
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -1078,6 +1079,29 @@
      * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
      * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}</code>).
      *
+     * <p>When annotating a view with this hint, it's recommended to use a date autofill value to
+     * avoid ambiguity when the autofill service provides a value for it. To understand why a
+     * value can be ambiguous, consider "April of 2020", which could be represented as either of
+     * the following options:
+     *
+     * <ul>
+     *   <li>{@code "04/2020"}
+     *   <li>{@code "4/2020"}
+     *   <li>{@code "2020/04"}
+     *   <li>{@code "2020/4"}
+     *   <li>{@code "April/2020"}
+     *   <li>{@code "Apr/2020"}
+     * </ul>
+     *
+     * <p>You define a date autofill value for the view by overriding the following methods:
+     *
+     * <ol>
+     *   <li>{@link #getAutofillType()} to return {@link #AUTOFILL_TYPE_DATE}.
+     *   <li>{@link #getAutofillValue()} to return a
+     *       {@link AutofillValue#forDate(long) date autofillvalue}.
+     *   <li>{@link #autofill(AutofillValue)} to expect a data autofillvalue.
+     * </ol>
+     *
      * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
      */
     public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE =
@@ -1090,6 +1114,22 @@
      * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
      * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}</code>).
      *
+     * <p>When annotating a view with this hint, it's recommended to use a text autofill value
+     * whose value is the numerical representation of the month, starting on {@code 1} to avoid
+     * ambiguity when the autofill service provides a value for it. To understand why a
+     * value can be ambiguous, consider "January", which could be represented as either of
+     *
+     * <ul>
+     *   <li>{@code "1"}: recommended way.
+     *   <li>{@code "0"}: if following the {@link Calendar#MONTH} convention.
+     *   <li>{@code "January"}: full name, in English.
+     *   <li>{@code "jan"}: abbreviated name, in English.
+     *   <li>{@code "Janeiro"}: full name, in another language.
+     * </ul>
+     *
+     * <p>Another recommended approach is to use a date autofill value - see
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} for more details.
+     *
      * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
      */
     public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH =
@@ -3702,15 +3742,90 @@
      * @hide
      */
     @ViewDebug.ExportedProperty(flagMapping = {
-        @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
-                                equals = SYSTEM_UI_FLAG_LOW_PROFILE,
-                                name = "SYSTEM_UI_FLAG_LOW_PROFILE", outputIf = true),
-        @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
-                                equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
-                                name = "SYSTEM_UI_FLAG_HIDE_NAVIGATION", outputIf = true),
-        @ViewDebug.FlagToString(mask = PUBLIC_STATUS_BAR_VISIBILITY_MASK,
-                                equals = SYSTEM_UI_FLAG_VISIBLE,
-                                name = "SYSTEM_UI_FLAG_VISIBLE", outputIf = true)
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
+                    equals = SYSTEM_UI_FLAG_LOW_PROFILE,
+                    name = "LOW_PROFILE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+                    equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+                    name = "HIDE_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_FULLSCREEN,
+                    equals = SYSTEM_UI_FLAG_FULLSCREEN,
+                    name = "FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                    name = "LAYOUT_STABLE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+                    name = "LAYOUT_HIDE_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+                    name = "LAYOUT_FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE,
+                    equals = SYSTEM_UI_FLAG_IMMERSIVE,
+                    name = "IMMERSIVE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+                    equals = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+                    name = "IMMERSIVE_STICKY"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                    equals = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                    name = "LIGHT_STATUS_BAR"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                    equals = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                    name = "LIGHT_NAVIGATION_BAR"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_EXPAND,
+                    equals = STATUS_BAR_DISABLE_EXPAND,
+                    name = "STATUS_BAR_DISABLE_EXPAND"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_ICONS"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_ALERTS"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_TICKER"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SYSTEM_INFO,
+                    equals = STATUS_BAR_DISABLE_SYSTEM_INFO,
+                    name = "STATUS_BAR_DISABLE_SYSTEM_INFO"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_HOME,
+                    equals = STATUS_BAR_DISABLE_HOME,
+                    name = "STATUS_BAR_DISABLE_HOME"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_BACK,
+                    equals = STATUS_BAR_DISABLE_BACK,
+                    name = "STATUS_BAR_DISABLE_BACK"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_CLOCK,
+                    equals = STATUS_BAR_DISABLE_CLOCK,
+                    name = "STATUS_BAR_DISABLE_CLOCK"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_RECENT,
+                    equals = STATUS_BAR_DISABLE_RECENT,
+                    name = "STATUS_BAR_DISABLE_RECENT"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SEARCH,
+                    equals = STATUS_BAR_DISABLE_SEARCH,
+                    name = "STATUS_BAR_DISABLE_SEARCH"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSIENT,
+                    equals = STATUS_BAR_TRANSIENT,
+                    name = "STATUS_BAR_TRANSIENT"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSIENT,
+                    equals = NAVIGATION_BAR_TRANSIENT,
+                    name = "NAVIGATION_BAR_TRANSIENT"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_UNHIDE,
+                    equals = STATUS_BAR_UNHIDE,
+                    name = "STATUS_BAR_UNHIDE"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_UNHIDE,
+                    equals = NAVIGATION_BAR_UNHIDE,
+                    name = "NAVIGATION_BAR_UNHIDE"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSLUCENT,
+                    equals = STATUS_BAR_TRANSLUCENT,
+                    name = "STATUS_BAR_TRANSLUCENT"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSLUCENT,
+                    equals = NAVIGATION_BAR_TRANSLUCENT,
+                    name = "NAVIGATION_BAR_TRANSLUCENT"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSPARENT,
+                    equals = NAVIGATION_BAR_TRANSPARENT,
+                    name = "NAVIGATION_BAR_TRANSPARENT"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSPARENT,
+                    equals = STATUS_BAR_TRANSPARENT,
+                    name = "STATUS_BAR_TRANSPARENT")
     }, formatToHexString = true)
     int mSystemUiVisibility;
 
@@ -15414,7 +15529,13 @@
      * {@code dirty}.
      *
      * @param dirty the rectangle representing the bounds of the dirty region
+     *
+     * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+     * the importance of the dirty rectangle. In API 21 the given rectangle is
+     * ignored entirely in favor of an internally-calculated area instead.
+     * Because of this, clients are encouraged to just call {@link #invalidate()}.
      */
+    @Deprecated
     public void invalidate(Rect dirty) {
         final int scrollX = mScrollX;
         final int scrollY = mScrollY;
@@ -15435,7 +15556,13 @@
      * @param t the top position of the dirty region
      * @param r the right position of the dirty region
      * @param b the bottom position of the dirty region
+     *
+     * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+     * the importance of the dirty rectangle. In API 21 the given rectangle is
+     * ignored entirely in favor of an internally-calculated area instead.
+     * Because of this, clients are encouraged to just call {@link #invalidate()}.
      */
+    @Deprecated
     public void invalidate(int l, int t, int r, int b) {
         final int scrollX = mScrollX;
         final int scrollY = mScrollY;
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
index 66c0578..3426485 100644
--- a/android/view/ViewDebug.java
+++ b/android/view/ViewDebug.java
@@ -1375,6 +1375,81 @@
         }
     }
 
+    /**
+     * Converts an integer from a field that is mapped with {@link IntToString} to its string
+     * representation.
+     *
+     * @param clazz The class the field is defined on.
+     * @param field The field on which the {@link ExportedProperty} is defined on.
+     * @param integer The value to convert.
+     * @return The value converted into its string representation.
+     * @hide
+     */
+    public static String intToString(Class<?> clazz, String field, int integer) {
+        final IntToString[] mapping = getMapping(clazz, field);
+        if (mapping == null) {
+            return Integer.toString(integer);
+        }
+        final int count = mapping.length;
+        for (int j = 0; j < count; j++) {
+            final IntToString map = mapping[j];
+            if (map.from() == integer) {
+                return map.to();
+            }
+        }
+        return Integer.toString(integer);
+    }
+
+    /**
+     * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
+     * representation.
+     *
+     * @param clazz The class the field is defined on.
+     * @param field The field on which the {@link ExportedProperty} is defined on.
+     * @param flags The flags to convert.
+     * @return The flags converted into their string representations.
+     * @hide
+     */
+    public static String flagsToString(Class<?> clazz, String field, int flags) {
+        final FlagToString[] mapping = getFlagMapping(clazz, field);
+        if (mapping == null) {
+            return Integer.toHexString(flags);
+        }
+        final StringBuilder result = new StringBuilder();
+        final int count = mapping.length;
+        for (int j = 0; j < count; j++) {
+            final FlagToString flagMapping = mapping[j];
+            final boolean ifTrue = flagMapping.outputIf();
+            final int maskResult = flags & flagMapping.mask();
+            final boolean test = maskResult == flagMapping.equals();
+            if (test && ifTrue) {
+                final String name = flagMapping.name();
+                result.append(name).append(' ');
+            }
+        }
+        if (result.length() > 0) {
+            result.deleteCharAt(result.length() - 1);
+        }
+        return result.toString();
+    }
+
+    private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
+        try {
+            return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
+                    .flagMapping();
+        } catch (NoSuchFieldException e) {
+            return null;
+        }
+    }
+
+    private static IntToString[] getMapping(Class<?> clazz, String field) {
+        try {
+            return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
+        } catch (NoSuchFieldException e) {
+            return null;
+        }
+    }
+
     private static void exportUnrolledArray(Context context, BufferedWriter out,
             ExportedProperty property, int[] array, String prefix, String suffix)
             throws IOException {
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 415aad5..71106ad 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -366,7 +366,7 @@
 
     // These can be accessed by any thread, must be protected with a lock.
     // Surface can never be reassigned or cleared (use Surface.clear()).
-    final Surface mSurface = new Surface();
+    public final Surface mSurface = new Surface();
 
     boolean mAdded;
     boolean mAddedTouchMode;
@@ -512,7 +512,7 @@
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
         if (!sCompatibilityDone) {
-            sAlwaysAssignFocus = true;
+            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
 
             sCompatibilityDone = true;
         }
@@ -7714,7 +7714,7 @@
         public void onAccessibilityStateChanged(boolean enabled) {
             if (enabled) {
                 ensureConnection();
-                if (mAttachInfo.mHasWindowFocus) {
+                if (mAttachInfo.mHasWindowFocus && (mView != null)) {
                     mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
                     View focusedView = mView.findFocus();
                     if (focusedView != null && focusedView != mView) {
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
index 0ecd20d..f671c34 100644
--- a/android/view/ViewStructure.java
+++ b/android/view/ViewStructure.java
@@ -378,7 +378,7 @@
      *
      * <p>Typically used when the view is a container for an HTML document.
      *
-     * @param domain URL representing the domain; only the host part will be used.
+     * @param domain RFC 2396-compliant URI representing the domain.
      */
     public abstract void setWebDomain(@Nullable String domain);
 
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index e56a82f..c29a1da 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+
 import android.Manifest.permission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -268,93 +270,93 @@
          */
         @ViewDebug.ExportedProperty(mapping = {
                 @ViewDebug.IntToString(from = TYPE_BASE_APPLICATION,
-                        to = "TYPE_BASE_APPLICATION"),
+                        to = "BASE_APPLICATION"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION,
-                        to = "TYPE_APPLICATION"),
+                        to = "APPLICATION"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_STARTING,
-                        to = "TYPE_APPLICATION_STARTING"),
+                        to = "APPLICATION_STARTING"),
                 @ViewDebug.IntToString(from = TYPE_DRAWN_APPLICATION,
-                        to = "TYPE_DRAWN_APPLICATION"),
+                        to = "DRAWN_APPLICATION"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL,
-                        to = "TYPE_APPLICATION_PANEL"),
+                        to = "APPLICATION_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA,
-                        to = "TYPE_APPLICATION_MEDIA"),
+                        to = "APPLICATION_MEDIA"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL,
-                        to = "TYPE_APPLICATION_SUB_PANEL"),
+                        to = "APPLICATION_SUB_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL,
-                        to = "TYPE_APPLICATION_ABOVE_SUB_PANEL"),
+                        to = "APPLICATION_ABOVE_SUB_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG,
-                        to = "TYPE_APPLICATION_ATTACHED_DIALOG"),
+                        to = "APPLICATION_ATTACHED_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY,
-                        to = "TYPE_APPLICATION_MEDIA_OVERLAY"),
+                        to = "APPLICATION_MEDIA_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_STATUS_BAR,
-                        to = "TYPE_STATUS_BAR"),
+                        to = "STATUS_BAR"),
                 @ViewDebug.IntToString(from = TYPE_SEARCH_BAR,
-                        to = "TYPE_SEARCH_BAR"),
+                        to = "SEARCH_BAR"),
                 @ViewDebug.IntToString(from = TYPE_PHONE,
-                        to = "TYPE_PHONE"),
+                        to = "PHONE"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT,
-                        to = "TYPE_SYSTEM_ALERT"),
+                        to = "SYSTEM_ALERT"),
                 @ViewDebug.IntToString(from = TYPE_TOAST,
-                        to = "TYPE_TOAST"),
+                        to = "TOAST"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY,
-                        to = "TYPE_SYSTEM_OVERLAY"),
+                        to = "SYSTEM_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE,
-                        to = "TYPE_PRIORITY_PHONE"),
+                        to = "PRIORITY_PHONE"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG,
-                        to = "TYPE_SYSTEM_DIALOG"),
+                        to = "SYSTEM_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG,
-                        to = "TYPE_KEYGUARD_DIALOG"),
+                        to = "KEYGUARD_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR,
-                        to = "TYPE_SYSTEM_ERROR"),
+                        to = "SYSTEM_ERROR"),
                 @ViewDebug.IntToString(from = TYPE_INPUT_METHOD,
-                        to = "TYPE_INPUT_METHOD"),
+                        to = "INPUT_METHOD"),
                 @ViewDebug.IntToString(from = TYPE_INPUT_METHOD_DIALOG,
-                        to = "TYPE_INPUT_METHOD_DIALOG"),
+                        to = "INPUT_METHOD_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_WALLPAPER,
-                        to = "TYPE_WALLPAPER"),
+                        to = "WALLPAPER"),
                 @ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL,
-                        to = "TYPE_STATUS_BAR_PANEL"),
+                        to = "STATUS_BAR_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_SECURE_SYSTEM_OVERLAY,
-                        to = "TYPE_SECURE_SYSTEM_OVERLAY"),
+                        to = "SECURE_SYSTEM_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_DRAG,
-                        to = "TYPE_DRAG"),
+                        to = "DRAG"),
                 @ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL,
-                        to = "TYPE_STATUS_BAR_SUB_PANEL"),
+                        to = "STATUS_BAR_SUB_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_POINTER,
-                        to = "TYPE_POINTER"),
+                        to = "POINTER"),
                 @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR,
-                        to = "TYPE_NAVIGATION_BAR"),
+                        to = "NAVIGATION_BAR"),
                 @ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY,
-                        to = "TYPE_VOLUME_OVERLAY"),
+                        to = "VOLUME_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS,
-                        to = "TYPE_BOOT_PROGRESS"),
+                        to = "BOOT_PROGRESS"),
                 @ViewDebug.IntToString(from = TYPE_INPUT_CONSUMER,
-                        to = "TYPE_INPUT_CONSUMER"),
+                        to = "INPUT_CONSUMER"),
                 @ViewDebug.IntToString(from = TYPE_DREAM,
-                        to = "TYPE_DREAM"),
+                        to = "DREAM"),
                 @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL,
-                        to = "TYPE_NAVIGATION_BAR_PANEL"),
+                        to = "NAVIGATION_BAR_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY,
-                        to = "TYPE_DISPLAY_OVERLAY"),
+                        to = "DISPLAY_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY,
-                        to = "TYPE_MAGNIFICATION_OVERLAY"),
+                        to = "MAGNIFICATION_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_PRESENTATION,
-                        to = "TYPE_PRESENTATION"),
+                        to = "PRESENTATION"),
                 @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION,
-                        to = "TYPE_PRIVATE_PRESENTATION"),
+                        to = "PRIVATE_PRESENTATION"),
                 @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION,
-                        to = "TYPE_VOICE_INTERACTION"),
+                        to = "VOICE_INTERACTION"),
                 @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING,
-                        to = "TYPE_VOICE_INTERACTION_STARTING"),
+                        to = "VOICE_INTERACTION_STARTING"),
                 @ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER,
-                        to = "TYPE_DOCK_DIVIDER"),
+                        to = "DOCK_DIVIDER"),
                 @ViewDebug.IntToString(from = TYPE_QS_DIALOG,
-                        to = "TYPE_QS_DIALOG"),
+                        to = "QS_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_SCREENSHOT,
-                        to = "TYPE_SCREENSHOT"),
+                        to = "SCREENSHOT"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_OVERLAY,
-                        to = "TYPE_APPLICATION_OVERLAY")
+                        to = "APPLICATION_OVERLAY")
         })
         public int type;
 
@@ -1198,63 +1200,69 @@
          */
         @ViewDebug.ExportedProperty(flagMapping = {
             @ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
-                    name = "FLAG_ALLOW_LOCK_WHILE_SCREEN_ON"),
+                    name = "ALLOW_LOCK_WHILE_SCREEN_ON"),
             @ViewDebug.FlagToString(mask = FLAG_DIM_BEHIND, equals = FLAG_DIM_BEHIND,
-                    name = "FLAG_DIM_BEHIND"),
+                    name = "DIM_BEHIND"),
             @ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
-                    name = "FLAG_BLUR_BEHIND"),
+                    name = "BLUR_BEHIND"),
             @ViewDebug.FlagToString(mask = FLAG_NOT_FOCUSABLE, equals = FLAG_NOT_FOCUSABLE,
-                    name = "FLAG_NOT_FOCUSABLE"),
+                    name = "NOT_FOCUSABLE"),
             @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCHABLE, equals = FLAG_NOT_TOUCHABLE,
-                    name = "FLAG_NOT_TOUCHABLE"),
+                    name = "NOT_TOUCHABLE"),
             @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCH_MODAL, equals = FLAG_NOT_TOUCH_MODAL,
-                    name = "FLAG_NOT_TOUCH_MODAL"),
+                    name = "NOT_TOUCH_MODAL"),
             @ViewDebug.FlagToString(mask = FLAG_TOUCHABLE_WHEN_WAKING, equals = FLAG_TOUCHABLE_WHEN_WAKING,
-                    name = "FLAG_TOUCHABLE_WHEN_WAKING"),
+                    name = "TOUCHABLE_WHEN_WAKING"),
             @ViewDebug.FlagToString(mask = FLAG_KEEP_SCREEN_ON, equals = FLAG_KEEP_SCREEN_ON,
-                    name = "FLAG_KEEP_SCREEN_ON"),
+                    name = "KEEP_SCREEN_ON"),
             @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_SCREEN, equals = FLAG_LAYOUT_IN_SCREEN,
-                    name = "FLAG_LAYOUT_IN_SCREEN"),
+                    name = "LAYOUT_IN_SCREEN"),
             @ViewDebug.FlagToString(mask = FLAG_LAYOUT_NO_LIMITS, equals = FLAG_LAYOUT_NO_LIMITS,
-                    name = "FLAG_LAYOUT_NO_LIMITS"),
+                    name = "LAYOUT_NO_LIMITS"),
             @ViewDebug.FlagToString(mask = FLAG_FULLSCREEN, equals = FLAG_FULLSCREEN,
-                    name = "FLAG_FULLSCREEN"),
+                    name = "FULLSCREEN"),
             @ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN, equals = FLAG_FORCE_NOT_FULLSCREEN,
-                    name = "FLAG_FORCE_NOT_FULLSCREEN"),
+                    name = "FORCE_NOT_FULLSCREEN"),
             @ViewDebug.FlagToString(mask = FLAG_DITHER, equals = FLAG_DITHER,
-                    name = "FLAG_DITHER"),
+                    name = "DITHER"),
             @ViewDebug.FlagToString(mask = FLAG_SECURE, equals = FLAG_SECURE,
-                    name = "FLAG_SECURE"),
+                    name = "SECURE"),
             @ViewDebug.FlagToString(mask = FLAG_SCALED, equals = FLAG_SCALED,
-                    name = "FLAG_SCALED"),
+                    name = "SCALED"),
             @ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES, equals = FLAG_IGNORE_CHEEK_PRESSES,
-                    name = "FLAG_IGNORE_CHEEK_PRESSES"),
+                    name = "IGNORE_CHEEK_PRESSES"),
             @ViewDebug.FlagToString(mask = FLAG_LAYOUT_INSET_DECOR, equals = FLAG_LAYOUT_INSET_DECOR,
-                    name = "FLAG_LAYOUT_INSET_DECOR"),
+                    name = "LAYOUT_INSET_DECOR"),
             @ViewDebug.FlagToString(mask = FLAG_ALT_FOCUSABLE_IM, equals = FLAG_ALT_FOCUSABLE_IM,
-                    name = "FLAG_ALT_FOCUSABLE_IM"),
+                    name = "ALT_FOCUSABLE_IM"),
             @ViewDebug.FlagToString(mask = FLAG_WATCH_OUTSIDE_TOUCH, equals = FLAG_WATCH_OUTSIDE_TOUCH,
-                    name = "FLAG_WATCH_OUTSIDE_TOUCH"),
+                    name = "WATCH_OUTSIDE_TOUCH"),
             @ViewDebug.FlagToString(mask = FLAG_SHOW_WHEN_LOCKED, equals = FLAG_SHOW_WHEN_LOCKED,
-                    name = "FLAG_SHOW_WHEN_LOCKED"),
+                    name = "SHOW_WHEN_LOCKED"),
             @ViewDebug.FlagToString(mask = FLAG_SHOW_WALLPAPER, equals = FLAG_SHOW_WALLPAPER,
-                    name = "FLAG_SHOW_WALLPAPER"),
+                    name = "SHOW_WALLPAPER"),
             @ViewDebug.FlagToString(mask = FLAG_TURN_SCREEN_ON, equals = FLAG_TURN_SCREEN_ON,
-                    name = "FLAG_TURN_SCREEN_ON"),
+                    name = "TURN_SCREEN_ON"),
             @ViewDebug.FlagToString(mask = FLAG_DISMISS_KEYGUARD, equals = FLAG_DISMISS_KEYGUARD,
-                    name = "FLAG_DISMISS_KEYGUARD"),
+                    name = "DISMISS_KEYGUARD"),
             @ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH,
-                    name = "FLAG_SPLIT_TOUCH"),
+                    name = "SPLIT_TOUCH"),
             @ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED,
-                    name = "FLAG_HARDWARE_ACCELERATED"),
-            @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
-                    name = "FLAG_LOCAL_FOCUS_MODE"),
+                    name = "HARDWARE_ACCELERATED"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_OVERSCAN, equals = FLAG_LAYOUT_IN_OVERSCAN,
+                    name = "LOCAL_FOCUS_MODE"),
             @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS,
-                    name = "FLAG_TRANSLUCENT_STATUS"),
+                    name = "TRANSLUCENT_STATUS"),
             @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION,
-                    name = "FLAG_TRANSLUCENT_NAVIGATION"),
+                    name = "TRANSLUCENT_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
+                    name = "LOCAL_FOCUS_MODE"),
+            @ViewDebug.FlagToString(mask = FLAG_SLIPPERY, equals = FLAG_SLIPPERY,
+                    name = "FLAG_SLIPPERY"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_ATTACHED_IN_DECOR, equals = FLAG_LAYOUT_ATTACHED_IN_DECOR,
+                    name = "FLAG_LAYOUT_ATTACHED_IN_DECOR"),
             @ViewDebug.FlagToString(mask = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, equals = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                    name = "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS")
+                    name = "DRAWS_SYSTEM_BAR_BACKGROUNDS")
         }, formatToHexString = true)
         public int flags;
 
@@ -1438,6 +1446,88 @@
          * Control flags that are private to the platform.
          * @hide
          */
+        @ViewDebug.ExportedProperty(flagMapping = {
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+                        equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+                        name = "FAKE_HARDWARE_ACCELERATED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+                        equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+                        name = "FORCE_HARDWARE_ACCELERATED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+                        equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+                        name = "WANTS_OFFSET_NOTIFICATIONS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+                        equals = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+                        name = "SHOW_FOR_ALL_USERS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+                        equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+                        name = "NO_MOVE_ANIMATION"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+                        equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+                        name = "COMPATIBLE_WINDOW"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SYSTEM_ERROR,
+                        equals = PRIVATE_FLAG_SYSTEM_ERROR,
+                        name = "SYSTEM_ERROR"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+                        equals = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+                        name = "INHERIT_TRANSLUCENT_DECOR"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_KEYGUARD,
+                        equals = PRIVATE_FLAG_KEYGUARD,
+                        name = "KEYGUARD"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                        equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                        name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+                        equals = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+                        name = "FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+                        equals = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+                        name = "PRESERVE_GEOMETRY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+                        equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+                        name = "FORCE_DECOR_VIEW_VISIBILITY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+                        equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+                        name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+                        equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+                        name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+                        equals = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+                        name = "FORCE_DRAW_STATUS_BAR_BACKGROUND"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                        equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                        name = "SUSTAINED_PERFORMANCE_MODE"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        equals = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        name = "HIDE_NON_SYSTEM_OVERLAY_WINDOWS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                        equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                        name = "IS_ROUNDED_CORNERS_OVERLAY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+                        equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+                        name = "ACQUIRES_SLEEP_TOKEN")
+        })
         @TestApi
         public int privateFlags;
 
@@ -1977,7 +2067,7 @@
          * @hide
          */
         @ActivityInfo.ColorMode
-        private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+        private int mColorMode = COLOR_MODE_DEFAULT;
 
         public LayoutParams() {
             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
@@ -2442,9 +2532,15 @@
 
         @Override
         public String toString() {
+            return toString("");
+        }
+
+        /**
+         * @hide
+         */
+        public String toString(String prefix) {
             StringBuilder sb = new StringBuilder(256);
-            sb.append("WM.LayoutParams{");
-            sb.append("(");
+            sb.append("{(");
             sb.append(x);
             sb.append(',');
             sb.append(y);
@@ -2464,26 +2560,19 @@
                 sb.append(verticalMargin);
             }
             if (gravity != 0) {
-                sb.append(" gr=#");
-                sb.append(Integer.toHexString(gravity));
+                sb.append(" gr=");
+                sb.append(Gravity.toString(gravity));
             }
             if (softInputMode != 0) {
-                sb.append(" sim=#");
-                sb.append(Integer.toHexString(softInputMode));
+                sb.append(" sim={");
+                sb.append(softInputModeToString(softInputMode));
+                sb.append('}');
             }
             sb.append(" ty=");
-            sb.append(type);
-            sb.append(" fl=#");
-            sb.append(Integer.toHexString(flags));
-            if (privateFlags != 0) {
-                if ((privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
-                    sb.append(" compatible=true");
-                }
-                sb.append(" pfl=0x").append(Integer.toHexString(privateFlags));
-            }
+            sb.append(ViewDebug.intToString(LayoutParams.class, "type", type));
             if (format != PixelFormat.OPAQUE) {
                 sb.append(" fmt=");
-                sb.append(format);
+                sb.append(PixelFormat.formatToString(format));
             }
             if (windowAnimations != 0) {
                 sb.append(" wanim=0x");
@@ -2491,7 +2580,7 @@
             }
             if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                 sb.append(" or=");
-                sb.append(screenOrientation);
+                sb.append(ActivityInfo.screenOrientationToString(screenOrientation));
             }
             if (alpha != 1.0f) {
                 sb.append(" alpha=");
@@ -2507,7 +2596,7 @@
             }
             if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
                 sb.append(" rotAnim=");
-                sb.append(rotationAnimation);
+                sb.append(rotationAnimationToString(rotationAnimation));
             }
             if (preferredRefreshRate != 0) {
                 sb.append(" preferredRefreshRate=");
@@ -2517,20 +2606,12 @@
                 sb.append(" preferredDisplayMode=");
                 sb.append(preferredDisplayModeId);
             }
-            if (systemUiVisibility != 0) {
-                sb.append(" sysui=0x");
-                sb.append(Integer.toHexString(systemUiVisibility));
-            }
-            if (subtreeSystemUiVisibility != 0) {
-                sb.append(" vsysui=0x");
-                sb.append(Integer.toHexString(subtreeSystemUiVisibility));
-            }
             if (hasSystemUiListeners) {
                 sb.append(" sysuil=");
                 sb.append(hasSystemUiListeners);
             }
             if (inputFeatures != 0) {
-                sb.append(" if=0x").append(Integer.toHexString(inputFeatures));
+                sb.append(" if=").append(inputFeatureToString(inputFeatures));
             }
             if (userActivityTimeout >= 0) {
                 sb.append(" userActivityTimeout=").append(userActivityTimeout);
@@ -2546,11 +2627,30 @@
                     sb.append(" (!preservePreviousSurfaceInsets)");
                 }
             }
-            if (needsMenuKey != NEEDS_MENU_UNSET) {
-                sb.append(" needsMenuKey=");
-                sb.append(needsMenuKey);
+            if (needsMenuKey == NEEDS_MENU_SET_TRUE) {
+                sb.append(" needsMenuKey");
             }
-            sb.append(" colorMode=").append(mColorMode);
+            if (mColorMode != COLOR_MODE_DEFAULT) {
+                sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
+            }
+            sb.append(System.lineSeparator());
+            sb.append(prefix).append("  fl=").append(
+                    ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+            if (privateFlags != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  pfl=").append(ViewDebug.flagsToString(
+                        LayoutParams.class, "privateFlags", privateFlags));
+            }
+            if (systemUiVisibility != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  sysui=").append(ViewDebug.flagsToString(
+                        View.class, "mSystemUiVisibility", systemUiVisibility));
+            }
+            if (subtreeSystemUiVisibility != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  vsysui=").append(ViewDebug.flagsToString(
+                        View.class, "mSystemUiVisibility", subtreeSystemUiVisibility));
+            }
             sb.append('}');
             return sb.toString();
         }
@@ -2634,5 +2734,88 @@
                     && width == WindowManager.LayoutParams.MATCH_PARENT
                     && height == WindowManager.LayoutParams.MATCH_PARENT;
         }
+
+        private static String softInputModeToString(@SoftInputModeFlags int softInputMode) {
+            final StringBuilder result = new StringBuilder();
+            final int state = softInputMode & SOFT_INPUT_MASK_STATE;
+            if (state != 0) {
+                result.append("state=");
+                switch (state) {
+                    case SOFT_INPUT_STATE_UNCHANGED:
+                        result.append("unchanged");
+                        break;
+                    case SOFT_INPUT_STATE_HIDDEN:
+                        result.append("hidden");
+                        break;
+                    case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                        result.append("always_hidden");
+                        break;
+                    case SOFT_INPUT_STATE_VISIBLE:
+                        result.append("visible");
+                        break;
+                    case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                        result.append("always_visible");
+                        break;
+                    default:
+                        result.append(state);
+                        break;
+                }
+                result.append(' ');
+            }
+            final int adjust = softInputMode & SOFT_INPUT_MASK_ADJUST;
+            if (adjust != 0) {
+                result.append("adjust=");
+                switch (adjust) {
+                    case SOFT_INPUT_ADJUST_RESIZE:
+                        result.append("resize");
+                        break;
+                    case SOFT_INPUT_ADJUST_PAN:
+                        result.append("pan");
+                        break;
+                    case SOFT_INPUT_ADJUST_NOTHING:
+                        result.append("nothing");
+                        break;
+                    default:
+                        result.append(adjust);
+                        break;
+                }
+                result.append(' ');
+            }
+            if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                result.append("forwardNavigation").append(' ');
+            }
+            result.deleteCharAt(result.length() - 1);
+            return result.toString();
+        }
+
+        private static String rotationAnimationToString(int rotationAnimation) {
+            switch (rotationAnimation) {
+                case ROTATION_ANIMATION_UNSPECIFIED:
+                    return "UNSPECIFIED";
+                case ROTATION_ANIMATION_ROTATE:
+                    return "ROTATE";
+                case ROTATION_ANIMATION_CROSSFADE:
+                    return "CROSSFADE";
+                case ROTATION_ANIMATION_JUMPCUT:
+                    return "JUMPCUT";
+                case ROTATION_ANIMATION_SEAMLESS:
+                    return "SEAMLESS";
+                default:
+                    return Integer.toString(rotationAnimation);
+            }
+        }
+
+        private static String inputFeatureToString(int inputFeature) {
+            switch (inputFeature) {
+                case INPUT_FEATURE_DISABLE_POINTER_GESTURES:
+                    return "DISABLE_POINTER_GESTURES";
+                case INPUT_FEATURE_NO_INPUT_CHANNEL:
+                    return "NO_INPUT_CHANNEL";
+                case INPUT_FEATURE_DISABLE_USER_ACTIVITY:
+                    return "DISABLE_USER_ACTIVITY";
+                default:
+                    return Integer.toString(inputFeature);
+            }
+        }
     }
 }
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index 66506a1..da72535 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -592,9 +592,10 @@
         int getDockedDividerInsetsLw();
 
         /**
-         * Retrieves the {@param outBounds} from the stack with id {@param stackId}.
+         * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
+         * {@param activityType}.
          */
-        void getStackBounds(int stackId, Rect outBounds);
+        void getStackBounds(int windowingMode, int activityType, Rect outBounds);
 
         /**
          * Notifies window manager that {@link #isShowingDreamLw} has changed.
@@ -617,6 +618,38 @@
          * @param listener callback to call when display can be turned off
          */
         void screenTurningOff(ScreenOffListener listener);
+
+        /**
+         * Convert the lid state to a human readable format.
+         */
+        static String lidStateToString(int lid) {
+            switch (lid) {
+                case LID_ABSENT:
+                    return "LID_ABSENT";
+                case LID_CLOSED:
+                    return "LID_CLOSED";
+                case LID_OPEN:
+                    return "LID_OPEN";
+                default:
+                    return Integer.toString(lid);
+            }
+        }
+
+        /**
+         * Convert the camera lens state to a human readable format.
+         */
+        static String cameraLensStateToString(int lens) {
+            switch (lens) {
+                case CAMERA_LENS_COVER_ABSENT:
+                    return "CAMERA_LENS_COVER_ABSENT";
+                case CAMERA_LENS_UNCOVERED:
+                    return "CAMERA_LENS_UNCOVERED";
+                case CAMERA_LENS_COVERED:
+                    return "CAMERA_LENS_COVERED";
+                default:
+                    return Integer.toString(lens);
+            }
+        }
     }
 
     public interface PointerEventListener {
@@ -1750,4 +1783,34 @@
      * @return true if ready; false otherwise.
      */
     boolean canDismissBootAnimation();
+
+    /**
+     * Convert the user rotation mode to a human readable format.
+     */
+    static String userRotationModeToString(int mode) {
+        switch(mode) {
+            case USER_ROTATION_FREE:
+                return "USER_ROTATION_FREE";
+            case USER_ROTATION_LOCKED:
+                return "USER_ROTATION_LOCKED";
+            default:
+                return Integer.toString(mode);
+        }
+    }
+
+    /**
+     * Convert the off reason to a human readable format.
+     */
+    static String offReasonToString(int why) {
+        switch (why) {
+            case OFF_BECAUSE_OF_ADMIN:
+                return "OFF_BECAUSE_OF_ADMIN";
+            case OFF_BECAUSE_OF_USER:
+                return "OFF_BECAUSE_OF_USER";
+            case OFF_BECAUSE_OF_TIMEOUT:
+                return "OFF_BECAUSE_OF_TIMEOUT";
+            default:
+                return Integer.toString(why);
+        }
+    }
 }
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 11cb046..0b9bc57 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,46 +16,152 @@
 
 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.
- * Such events are generated when something notable happens in the user interface,
+ * 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,
  * 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
- * {@code android.accessibilityservice.AccessibilityService}.
+ * {@link android.accessibilityservice.AccessibilityService}.
  *
  * @see AccessibilityEvent
- * @see android.content.Context#getSystemService
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
  */
-@SuppressWarnings("UnusedDeclaration")
+@SystemService(Context.ACCESSIBILITY_SERVICE)
 public final class AccessibilityManager {
+    private static final boolean DEBUG = false;
 
-    private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
+    private static final String LOG_TAG = "AccessibilityManager";
 
+    /** @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;
 
     /**
-     * Listener for the accessibility state.
+     * 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}.
      */
     public interface AccessibilityStateChangeListener {
 
         /**
-         * Called back on change in the accessibility state.
+         * Called when the accessibility enabled state changes.
          *
          * @param enabled Whether accessibility is enabled.
          */
-        public void onAccessibilityStateChanged(boolean enabled);
+        void onAccessibilityStateChanged(boolean enabled);
     }
 
     /**
@@ -71,7 +177,24 @@
          *
          * @param enabled Whether touch exploration is enabled.
          */
-        public void onTouchExplorationStateChanged(boolean 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);
     }
 
     /**
@@ -79,6 +202,8 @@
      * 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 {
 
@@ -87,26 +212,72 @@
          *
          * @param enabled Whether high text contrast is enabled.
          */
-        public void onHighTextContrastStateChanged(boolean enabled);
+        void onHighTextContrastStateChanged(boolean enabled);
     }
 
     private final IAccessibilityManagerClient.Stub mClient =
             new IAccessibilityManagerClient.Stub() {
-                public void setState(int state) {
-                }
+        @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();
+        }
 
-                public void notifyServicesStateChanged() {
+        @Override
+        public void notifyServicesStateChanged() {
+            final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+            synchronized (mLock) {
+                if (mServicesStateChangeListeners.isEmpty()) {
+                    return;
                 }
+                listeners = new ArrayMap<>(mServicesStateChangeListeners);
+            }
 
-                public void setRelevantEventTypes(int eventTypes) {
-                }
-            };
+            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));
+            }
+        }
+
+        @Override
+        public void setRelevantEventTypes(int eventTypes) {
+            mRelevantEventTypes = 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;
     }
 
@@ -114,21 +285,68 @@
      * 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;
     }
 
     /**
-     * Returns if the {@link AccessibilityManager} is enabled.
+     * @hide
+     */
+    @VisibleForTesting
+    public Handler.Callback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Returns if the accessibility in the system is enabled.
      *
-     * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+     * @return True if accessibility is enabled, false otherwise.
      */
     public boolean isEnabled() {
-        return false;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsEnabled;
+        }
     }
 
     /**
@@ -137,7 +355,13 @@
      * @return True if touch exploration is enabled, false otherwise.
      */
     public boolean isTouchExplorationEnabled() {
-        return true;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsTouchExplorationEnabled;
+        }
     }
 
     /**
@@ -147,35 +371,169 @@
      * 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() {
-        return false;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsHighTextContrastEnabled;
+        }
     }
 
     /**
      * 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 interruption of the accessibility feedback from all accessibility services.
+     * Requests feedback interruption 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() {
-        return Collections.emptyList();
+        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);
     }
 
+    /**
+     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+     *
+     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+     */
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
-        return Collections.emptyList();
+        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();
+        }
     }
 
     /**
@@ -190,21 +548,48 @@
      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
      */
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
             int feedbackTypeFlags) {
-        return Collections.emptyList();
+        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();
+        }
     }
 
     /**
      * Registers an {@link AccessibilityStateChangeListener} for changes in
-     * the global accessibility state of the system.
+     * the global accessibility state of the system. Equivalent to calling
+     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addAccessibilityStateChangeListener(
-            AccessibilityStateChangeListener listener) {
+            @NonNull AccessibilityStateChangeListener listener) {
+        addAccessibilityStateChangeListener(listener, null);
         return true;
     }
 
@@ -218,22 +603,40 @@
      *                for a callback on the process's main handler.
      */
     public void addAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
+            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mAccessibilityStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
+    /**
+     * Unregisters an {@link AccessibilityStateChangeListener}.
+     *
+     * @param listener The listener.
+     * @return True if the listener was previously registered.
+     */
     public boolean removeAccessibilityStateChangeListener(
-            AccessibilityStateChangeListener listener) {
-        return true;
+            @NonNull AccessibilityStateChangeListener listener) {
+        synchronized (mLock) {
+            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+            mAccessibilityStateChangeListeners.remove(listener);
+            return (index >= 0);
+        }
     }
 
     /**
      * Registers a {@link TouchExplorationStateChangeListener} for changes in
-     * the global touch exploration state of the system.
+     * the global touch exploration state of the system. Equivalent to calling
+     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
+        addTouchExplorationStateChangeListener(listener, null);
         return true;
     }
 
@@ -247,17 +650,103 @@
      *                for a callback on the process's main handler.
      */
     public void addTouchExplorationStateChangeListener(
-            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
+            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mTouchExplorationStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
     /**
      * Unregisters a {@link TouchExplorationStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully unregistered.
+     * @return True if listener was previously registered.
      */
     public boolean removeTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        return true;
+        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);
     }
 
     /**
@@ -269,7 +758,12 @@
      * @hide
      */
     public void addHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
+            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mHighTextContrastStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
     /**
      * Unregisters a {@link HighTextContrastChangeListener}.
@@ -279,7 +773,51 @@
      * @hide
      */
     public void removeHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener) {}
+            @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;
+        }
+    }
 
     /**
      * Sets the current state and notifies listeners, if necessary.
@@ -287,14 +825,314 @@
      * @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 61cbce9..4fb2a99 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -37,14 +37,13 @@
 import android.service.autofill.FillEventHistory;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.DebugUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -202,9 +201,12 @@
      * Initial state of the autofill context, set when there is no session (i.e., when
      * {@link #mSessionId} is {@link #NO_SESSION}).
      *
+     * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to
+     * the server.
+     *
      * @hide
      */
-    public static final int STATE_UNKNOWN = 1;
+    public static final int STATE_UNKNOWN = 0;
 
     /**
      * State where the autofill context hasn't been {@link #commit() finished} nor
@@ -212,7 +214,18 @@
      *
      * @hide
      */
-    public static final int STATE_ACTIVE = 2;
+    public static final int STATE_ACTIVE = 1;
+
+    /**
+     * State where the autofill context was finished by the server because the autofill
+     * service could not autofill the page.
+     *
+     * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
+     * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
+     *
+     * @hide
+     */
+    public static final int STATE_FINISHED = 2;
 
     /**
      * State where the autofill context has been {@link #commit() finished} but the server still has
@@ -220,7 +233,7 @@
      *
      * @hide
      */
-    public static final int STATE_SHOWING_SAVE_UI = 4;
+    public static final int STATE_SHOWING_SAVE_UI = 3;
 
     /**
      * Makes an authentication id from a request id and a dataset id.
@@ -559,6 +572,14 @@
         }
         AutofillCallback callback = null;
         synchronized (mLock) {
+            if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
+                if (sVerbose) {
+                    Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
+                            + "): ignored on state " + getStateAsStringLocked());
+                }
+                return;
+            }
+
             ensureServiceClientAddedIfNeededLocked();
 
             if (!mEnabled) {
@@ -682,6 +703,14 @@
         }
         AutofillCallback callback = null;
         synchronized (mLock) {
+            if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
+                if (sVerbose) {
+                    Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
+                            + ", virtualId=" + virtualId
+                            + "): ignored on state " + getStateAsStringLocked());
+                }
+                return;
+            }
             ensureServiceClientAddedIfNeededLocked();
 
             if (!mEnabled) {
@@ -765,6 +794,10 @@
             }
 
             if (!mEnabled || !isActiveLocked()) {
+                if (sVerbose && mEnabled) {
+                    Log.v(TAG, "notifyValueChanged(" + view + "): ignoring on state "
+                            + getStateAsStringLocked());
+                }
                 return;
             }
 
@@ -904,10 +937,7 @@
     }
 
     private AutofillClient getClientLocked() {
-        if (mContext instanceof AutofillClient) {
-            return (AutofillClient) mContext;
-        }
-        return null;
+        return mContext.getAutofillClient();
     }
 
     /** @hide */
@@ -950,10 +980,13 @@
             @NonNull AutofillValue value, int flags) {
         if (sVerbose) {
             Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
-                    + ", flags=" + flags + ", state=" + mState);
+                    + ", flags=" + flags + ", state=" + getStateAsStringLocked());
         }
-        if (mState != STATE_UNKNOWN) {
-            if (sDebug) Log.d(TAG, "not starting session for " + id + " on state " + mState);
+        if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+            if (sVerbose) {
+                Log.v(TAG, "not automatically starting session for " + id
+                        + " on state " + getStateAsStringLocked());
+            }
             return;
         }
         try {
@@ -973,7 +1006,7 @@
     }
 
     private void finishSessionLocked() {
-        if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + mState);
+        if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked());
 
         if (!isActiveLocked()) return;
 
@@ -987,7 +1020,7 @@
     }
 
     private void cancelSessionLocked() {
-        if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + mState);
+        if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked());
 
         if (!isActiveLocked()) return;
 
@@ -1245,10 +1278,10 @@
                 }
             }
 
-            final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
-            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
-            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
-                    numApplied);
+            final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED)
+                    .setPackageName(mContext.getPackageName())
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
             mMetricsLogger.write(log);
         }
     }
@@ -1306,6 +1339,20 @@
         }
     }
 
+    /**
+     * Marks the state of the session as finished.
+     *
+     * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
+     *  FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
+     */
+    private void setSessionFinished(int newState) {
+        synchronized (mLock) {
+            if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState);
+            resetSessionLocked();
+            mState = newState;
+        }
+    }
+
     private void requestHideFillUi(AutofillId id) {
         final View anchor = findView(id);
         if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
@@ -1341,7 +1388,11 @@
         }
     }
 
-    private void notifyNoFillUi(int sessionId, AutofillId id) {
+    private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+        if (sVerbose) {
+            Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
+                    + ", finished=" + sessionFinished);
+        }
         final View anchor = findView(id);
         if (anchor == null) {
             return;
@@ -1361,7 +1412,11 @@
             } else {
                 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
             }
+        }
 
+        if (sessionFinished) {
+            // Callback call was "hijacked" to also update the session state.
+            setSessionFinished(STATE_FINISHED);
         }
     }
 
@@ -1434,8 +1489,7 @@
         pw.print(outerPrefix); pw.println("AutofillManager:");
         final String pfx = outerPrefix + "  ";
         pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
-        pw.print(pfx); pw.print("state: "); pw.println(
-                DebugUtils.flagsToString(AutofillManager.class, "STATE_", mState));
+        pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
         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);
@@ -1452,10 +1506,29 @@
         pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
     }
 
+    private String getStateAsStringLocked() {
+        switch (mState) {
+            case STATE_UNKNOWN:
+                return "STATE_UNKNOWN";
+            case STATE_ACTIVE:
+                return "STATE_ACTIVE";
+            case STATE_FINISHED:
+                return "STATE_FINISHED";
+            case STATE_SHOWING_SAVE_UI:
+                return "STATE_SHOWING_SAVE_UI";
+            default:
+                return "INVALID:" + mState;
+        }
+    }
+
     private boolean isActiveLocked() {
         return mState == STATE_ACTIVE;
     }
 
+    private boolean isFinishedLocked() {
+        return mState == STATE_FINISHED;
+    }
+
     private void post(Runnable runnable) {
         final AutofillClient client = getClientLocked();
         if (client == null) {
@@ -1787,10 +1860,10 @@
         }
 
         @Override
-        public void notifyNoFillUi(int sessionId, AutofillId id) {
+        public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() -> afm.notifyNoFillUi(sessionId, id));
+                afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
             }
         }
 
@@ -1823,7 +1896,15 @@
         public void setSaveUiState(int sessionId, boolean shown) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() ->afm.setSaveUiState(sessionId, shown));
+                afm.post(() -> afm.setSaveUiState(sessionId, shown));
+            }
+        }
+
+        @Override
+        public void setSessionFinished(int newState) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.setSessionFinished(newState));
             }
         }
     }
diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java
index 5f47638..b4688bb 100644
--- a/android/view/autofill/AutofillPopupWindow.java
+++ b/android/view/autofill/AutofillPopupWindow.java
@@ -47,6 +47,19 @@
     private final WindowPresenter mWindowPresenter;
     private WindowManager.LayoutParams mWindowLayoutParams;
 
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            /* ignore - handled by the super class */
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            dismiss();
+        }
+    };
+
     /**
      * Creates a popup window with a presenter owning the window and responsible for
      * showing/hiding/updating the backing window. This can be useful of the window is
@@ -208,7 +221,21 @@
         p.packageName = anchor.getContext().getPackageName();
         mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
                 anchor.getLayoutDirection());
-        return;
+    }
+
+    @Override
+    protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+        super.attachToAnchor(anchor, xoff, yoff, gravity);
+        anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+    }
+
+    @Override
+    protected void detachFromAnchor() {
+        final View anchor = getAnchor();
+        if (anchor != null) {
+            anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        }
+        super.detachFromAnchor();
     }
 
     @Override
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 1849368..8c3b8a2 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -41,10 +42,10 @@
     static final TextClassification EMPTY = new TextClassification.Builder().build();
 
     @NonNull private final String mText;
-    @Nullable private final Drawable mIcon;
-    @Nullable private final String mLabel;
-    @Nullable private final Intent mIntent;
-    @Nullable private final OnClickListener mOnClickListener;
+    @NonNull private final List<Drawable> mIcons;
+    @NonNull private final List<String> mLabels;
+    @NonNull private final List<Intent> mIntents;
+    @NonNull private final List<OnClickListener> mOnClickListeners;
     @NonNull private final EntityConfidence<String> mEntityConfidence;
     @NonNull private final List<String> mEntities;
     private int mLogType;
@@ -52,18 +53,21 @@
 
     private TextClassification(
             @Nullable String text,
-            @Nullable Drawable icon,
-            @Nullable String label,
-            @Nullable Intent intent,
-            @Nullable OnClickListener onClickListener,
+            @NonNull List<Drawable> icons,
+            @NonNull List<String> labels,
+            @NonNull List<Intent> intents,
+            @NonNull List<OnClickListener> onClickListeners,
             @NonNull EntityConfidence<String> entityConfidence,
             int logType,
             @NonNull String versionInfo) {
+        Preconditions.checkArgument(labels.size() == intents.size());
+        Preconditions.checkArgument(icons.size() == intents.size());
+        Preconditions.checkArgument(onClickListeners.size() == intents.size());
         mText = text;
-        mIcon = icon;
-        mLabel = label;
-        mIntent = intent;
-        mOnClickListener = onClickListener;
+        mIcons = icons;
+        mLabels = labels;
+        mIntents = intents;
+        mOnClickListeners = onClickListeners;
         mEntityConfidence = new EntityConfidence<>(entityConfidence);
         mEntities = mEntityConfidence.getEntities();
         mLogType = logType;
@@ -109,35 +113,106 @@
     }
 
     /**
-     * Returns an icon that may be rendered on a widget used to act on the classified text.
+     * Returns the number of actions that are available to act on the classified text.
+     * @see #getIntent(int)
+     * @see #getLabel(int)
+     * @see #getIcon(int)
+     * @see #getOnClickListener(int)
+     */
+    @IntRange(from = 0)
+    public int getActionCount() {
+        return mIntents.size();
+    }
+
+    /**
+     * Returns one of the icons that maybe rendered on a widget used to act on the classified text.
+     * @param index Index of the action to get the icon for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount() for the number of entities available.
+     * @see #getIntent(int)
+     * @see #getLabel(int)
+     * @see #getOnClickListener(int)
+     */
+    @Nullable
+    public Drawable getIcon(int index) {
+        return mIcons.get(index);
+    }
+
+    /**
+     * Returns an icon for the default intent that may be rendered on a widget used to act on the
+     * classified text.
      */
     @Nullable
     public Drawable getIcon() {
-        return mIcon;
+        return mIcons.isEmpty() ? null : mIcons.get(0);
     }
 
     /**
-     * Returns a label that may be rendered on a widget used to act on the classified text.
+     * Returns one of the labels that may be rendered on a widget used to act on the classified
+     * text.
+     * @param index Index of the action to get the label for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount()
+     * @see #getIntent(int)
+     * @see #getIcon(int)
+     * @see #getOnClickListener(int)
+     */
+    @Nullable
+    public CharSequence getLabel(int index) {
+        return mLabels.get(index);
+    }
+
+    /**
+     * Returns a label for the default intent that may be rendered on a widget used to act on the
+     * classified text.
      */
     @Nullable
     public CharSequence getLabel() {
-        return mLabel;
+        return mLabels.isEmpty() ? null : mLabels.get(0);
     }
 
     /**
-     * Returns an intent that may be fired to act on the classified text.
+     * Returns one of the intents that may be fired to act on the classified text.
+     * @param index Index of the action to get the intent for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount()
+     * @see #getLabel(int)
+     * @see #getIcon(int)
+     * @see #getOnClickListener(int)
+     */
+    @Nullable
+    public Intent getIntent(int index) {
+        return mIntents.get(index);
+    }
+
+    /**
+     * Returns the default intent that may be fired to act on the classified text.
      */
     @Nullable
     public Intent getIntent() {
-        return mIntent;
+        return mIntents.isEmpty() ? null : mIntents.get(0);
     }
 
     /**
-     * Returns an OnClickListener that may be triggered to act on the classified text.
+     * Returns one of the OnClickListeners that may be triggered to act on the classified text.
+     * @param index Index of the action to get the click listener for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount()
+     * @see #getIntent(int)
+     * @see #getLabel(int)
+     * @see #getIcon(int)
+     */
+    @Nullable
+    public OnClickListener getOnClickListener(int index) {
+        return mOnClickListeners.get(index);
+    }
+
+    /**
+     * Returns the default OnClickListener that may be triggered to act on the classified text.
      */
     @Nullable
     public OnClickListener getOnClickListener() {
-        return mOnClickListener;
+        return mOnClickListeners.isEmpty() ? null : mOnClickListeners.get(0);
     }
 
     /**
@@ -160,8 +235,8 @@
     @Override
     public String toString() {
         return String.format("TextClassification {"
-                        + "text=%s, entities=%s, label=%s, intent=%s}",
-                mText, mEntityConfidence, mLabel, mIntent);
+                        + "text=%s, entities=%s, labels=%s, intents=%s}",
+                mText, mEntityConfidence, mLabels, mIntents);
     }
 
     /**
@@ -184,10 +259,10 @@
     public static final class Builder {
 
         @NonNull private String mText;
-        @Nullable private Drawable mIcon;
-        @Nullable private String mLabel;
-        @Nullable private Intent mIntent;
-        @Nullable private OnClickListener mOnClickListener;
+        @NonNull private final List<Drawable> mIcons = new ArrayList<>();
+        @NonNull private final List<String> mLabels = new ArrayList<>();
+        @NonNull private final List<Intent> mIntents = new ArrayList<>();
+        @NonNull private final List<OnClickListener> mOnClickListeners = new ArrayList<>();
         @NonNull private final EntityConfidence<String> mEntityConfidence =
                 new EntityConfidence<>();
         private int mLogType;
@@ -216,26 +291,57 @@
         }
 
         /**
-         * Sets an icon that may be rendered on a widget used to act on the classified text.
+         * Adds an action that may be performed on the classified text. The label and icon are used
+         * for rendering of widgets that offer the intent. Actions should be added in order of
+         * priority and the first one will be treated as the default.
+         */
+        public Builder addAction(
+                Intent intent, @Nullable String label, @Nullable Drawable icon,
+                @Nullable OnClickListener onClickListener) {
+            mIntents.add(intent);
+            mLabels.add(label);
+            mIcons.add(icon);
+            mOnClickListeners.add(onClickListener);
+            return this;
+        }
+
+        /**
+         * Removes all actions.
+         */
+        public Builder clearActions() {
+            mIntents.clear();
+            mOnClickListeners.clear();
+            mLabels.clear();
+            mIcons.clear();
+            return this;
+        }
+
+        /**
+         * Sets the icon for the default action that may be rendered on a widget used to act on the
+         * classified text.
          */
         public Builder setIcon(@Nullable Drawable icon) {
-            mIcon = icon;
+            ensureDefaultActionAvailable();
+            mIcons.set(0, icon);
             return this;
         }
 
         /**
-         * Sets a label that may be rendered on a widget used to act on the classified text.
+         * Sets the label for the default action that may be rendered on a widget used to act on the
+         * classified text.
          */
         public Builder setLabel(@Nullable String label) {
-            mLabel = label;
+            ensureDefaultActionAvailable();
+            mLabels.set(0, label);
             return this;
         }
 
         /**
-         * Sets an intent that may be fired to act on the classified text.
+         * Sets the intent for the default action that may be fired to act on the classified text.
          */
         public Builder setIntent(@Nullable Intent intent) {
-            mIntent = intent;
+            ensureDefaultActionAvailable();
+            mIntents.set(0, intent);
             return this;
         }
 
@@ -249,10 +355,12 @@
         }
 
         /**
-         * Sets an OnClickListener that may be triggered to act on the classified text.
+         * Sets the OnClickListener for the default action that may be triggered to act on the
+         * classified text.
          */
         public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
-            mOnClickListener = onClickListener;
+            ensureDefaultActionAvailable();
+            mOnClickListeners.set(0, onClickListener);
             return this;
         }
 
@@ -266,11 +374,21 @@
         }
 
         /**
+         * Ensures that we have at we have storage for the default action.
+         */
+        private void ensureDefaultActionAvailable() {
+            if (mIntents.isEmpty()) mIntents.add(null);
+            if (mLabels.isEmpty()) mLabels.add(null);
+            if (mIcons.isEmpty()) mIcons.add(null);
+            if (mOnClickListeners.isEmpty()) mOnClickListeners.add(null);
+        }
+
+        /**
          * Builds and returns a {@link TextClassification} object.
          */
         public TextClassification build() {
             return new TextClassification(
-                    mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence,
+                    mText, mIcons, mLabels, mIntents, mOnClickListeners, mEntityConfidence,
                     mLogType, mVersionInfo);
         }
     }
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 7e93b78..2aa81a2 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -29,6 +29,7 @@
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.provider.Browser;
+import android.provider.ContactsContract;
 import android.text.Spannable;
 import android.text.TextUtils;
 import android.text.method.WordIterator;
@@ -356,7 +357,16 @@
         final String type = getHighestScoringType(classifications);
         builder.setLogType(IntentFactory.getLogType(type));
 
-        final Intent intent = IntentFactory.create(mContext, type, text.toString());
+        final List<Intent> intents = IntentFactory.create(mContext, type, text.toString());
+        for (Intent intent : intents) {
+            extendClassificationWithIntent(intent, builder);
+        }
+
+        return builder.setVersionInfo(getVersionInfo()).build();
+    }
+
+    /** Extends the classification with the intent if it can be resolved. */
+    private void extendClassificationWithIntent(Intent intent, TextClassification.Builder builder) {
         final PackageManager pm;
         final ResolveInfo resolveInfo;
         if (intent != null) {
@@ -367,30 +377,29 @@
             resolveInfo = null;
         }
         if (resolveInfo != null && resolveInfo.activityInfo != null) {
-            builder.setIntent(intent)
-                    .setOnClickListener(TextClassification.createStartActivityOnClickListener(
-                            mContext, intent));
-
             final String packageName = resolveInfo.activityInfo.packageName;
+            CharSequence label;
+            Drawable icon;
             if ("android".equals(packageName)) {
                 // Requires the chooser to find an activity to handle the intent.
-                builder.setLabel(IntentFactory.getLabel(mContext, type));
+                label = IntentFactory.getLabel(mContext, intent);
+                icon = null;
             } else {
                 // A default activity will handle the intent.
                 intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
-                Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+                icon = resolveInfo.activityInfo.loadIcon(pm);
                 if (icon == null) {
                     icon = resolveInfo.loadIcon(pm);
                 }
-                builder.setIcon(icon);
-                CharSequence label = resolveInfo.activityInfo.loadLabel(pm);
+                label = resolveInfo.activityInfo.loadLabel(pm);
                 if (label == null) {
                     label = resolveInfo.loadLabel(pm);
                 }
-                builder.setLabel(label != null ? label.toString() : null);
             }
+            builder.addAction(
+                    intent, label != null ? label.toString() : null, icon,
+                    TextClassification.createStartActivityOnClickListener(mContext, intent));
         }
-        return builder.setVersionInfo(getVersionInfo()).build();
     }
 
     private static int getHintFlags(CharSequence text, int start, int end) {
@@ -477,10 +486,11 @@
                     if (results.length > 0) {
                         final String type = getHighestScoringType(results);
                         if (matches(type, linkMask)) {
-                            final Intent intent = IntentFactory.create(
+                            // For links without disambiguation, we simply use the default intent.
+                            final List<Intent> intents = IntentFactory.create(
                                     context, type, text.substring(selectionStart, selectionEnd));
-                            if (hasActivityHandler(context, intent)) {
-                                final ClickableSpan span = createSpan(context, intent);
+                            if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
+                                final ClickableSpan span = createSpan(context, intents.get(0));
                                 spans.add(new SpanSpec(selectionStart, selectionEnd, span));
                             }
                         }
@@ -564,7 +574,7 @@
             };
         }
 
-        private static boolean hasActivityHandler(Context context, @Nullable Intent intent) {
+        private static boolean hasActivityHandler(Context context, Intent intent) {
             if (intent == null) {
                 return false;
             }
@@ -625,20 +635,32 @@
 
         private IntentFactory() {}
 
-        @Nullable
-        public static Intent create(Context context, String type, String text) {
+        @NonNull
+        public static List<Intent> create(Context context, String type, String text) {
+            final List<Intent> intents = new ArrayList<>();
             type = type.trim().toLowerCase(Locale.ENGLISH);
             text = text.trim();
             switch (type) {
                 case TextClassifier.TYPE_EMAIL:
-                    return new Intent(Intent.ACTION_SENDTO)
-                            .setData(Uri.parse(String.format("mailto:%s", text)));
+                    intents.add(new Intent(Intent.ACTION_SENDTO)
+                            .setData(Uri.parse(String.format("mailto:%s", text))));
+                    intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+                                    .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                                    .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+                    break;
                 case TextClassifier.TYPE_PHONE:
-                    return new Intent(Intent.ACTION_DIAL)
-                            .setData(Uri.parse(String.format("tel:%s", text)));
+                    intents.add(new Intent(Intent.ACTION_DIAL)
+                            .setData(Uri.parse(String.format("tel:%s", text))));
+                    intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                            .putExtra(ContactsContract.Intents.Insert.PHONE, text));
+                    intents.add(new Intent(Intent.ACTION_SENDTO)
+                            .setData(Uri.parse(String.format("smsto:%s", text))));
+                    break;
                 case TextClassifier.TYPE_ADDRESS:
-                    return new Intent(Intent.ACTION_VIEW)
-                            .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+                    intents.add(new Intent(Intent.ACTION_VIEW)
+                            .setData(Uri.parse(String.format("geo:0,0?q=%s", text))));
+                    break;
                 case TextClassifier.TYPE_URL:
                     final String httpPrefix = "http://";
                     final String httpsPrefix = "https://";
@@ -649,25 +671,47 @@
                     } else {
                         text = httpPrefix + text;
                     }
-                    return new Intent(Intent.ACTION_VIEW, Uri.parse(text))
-                            .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
-                default:
-                    return null;
+                    intents.add(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+                            .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
+                    break;
             }
+            return intents;
         }
 
         @Nullable
-        public static String getLabel(Context context, String type) {
-            type = type.trim().toLowerCase(Locale.ENGLISH);
-            switch (type) {
-                case TextClassifier.TYPE_EMAIL:
-                    return context.getString(com.android.internal.R.string.email);
-                case TextClassifier.TYPE_PHONE:
+        public static String getLabel(Context context, @Nullable Intent intent) {
+            if (intent == null || intent.getAction() == null) {
+                return null;
+            }
+            switch (intent.getAction()) {
+                case Intent.ACTION_DIAL:
                     return context.getString(com.android.internal.R.string.dial);
-                case TextClassifier.TYPE_ADDRESS:
-                    return context.getString(com.android.internal.R.string.map);
-                case TextClassifier.TYPE_URL:
-                    return context.getString(com.android.internal.R.string.browse);
+                case Intent.ACTION_SENDTO:
+                    switch (intent.getScheme()) {
+                        case "mailto":
+                            return context.getString(com.android.internal.R.string.email);
+                        case "smsto":
+                            return context.getString(com.android.internal.R.string.sms);
+                        default:
+                            return null;
+                    }
+                case Intent.ACTION_INSERT_OR_EDIT:
+                    switch (intent.getDataString()) {
+                        case ContactsContract.Contacts.CONTENT_ITEM_TYPE:
+                            return context.getString(com.android.internal.R.string.add_contact);
+                        default:
+                            return null;
+                    }
+                case Intent.ACTION_VIEW:
+                    switch (intent.getScheme()) {
+                        case "geo":
+                            return context.getString(com.android.internal.R.string.map);
+                        case "http": // fall through
+                        case "https":
+                            return context.getString(com.android.internal.R.string.browse);
+                        default:
+                            return null;
+                    }
                 default:
                     return null;
             }
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 8d88ba6..83af19b 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -581,6 +581,7 @@
                 case ActionType.SMART_SHARE:  // fall through
                 case ActionType.DRAG:  // fall through
                 case ActionType.ABANDON:  // fall through
+                case ActionType.OTHER:  // fall through
                     return true;
                 default:
                     return false;
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index 8e1f218..f368c74 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,58 +1,213 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * 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;
 
 /**
- * A stub class of TextServicesManager for Layout-Lib.
+ * 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>
+ *
  */
+@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
 public final class TextServicesManager {
-    private static final TextServicesManager sInstance = new TextServicesManager();
-    private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
+    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));
+    }
 
     /**
      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
      * @hide
      */
     public static TextServicesManager getInstance() {
-        return sInstance;
+        synchronized (TextServicesManager.class) {
+            if (sInstance == null) {
+                try {
+                    sInstance = new TextServicesManager();
+                } catch (ServiceNotFoundException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+            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) {
-        return null;
+        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;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo[] getEnabledSpellCheckers() {
-        return EMPTY_SPELL_CHECKER_INFO;
+        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();
+        }
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo getCurrentSpellChecker() {
-        return null;
+        try {
+            // Passing null as a locale for ICS
+            return mService.getCurrentSpellChecker(null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -60,13 +215,22 @@
      */
     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
             boolean allowImplicitlySelectedSubtype) {
-        return null;
+        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();
+        }
     }
 
     /**
      * @hide
      */
     public boolean isSpellCheckerEnabled() {
-        return false;
+        try {
+            return mService.isSpellCheckerEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 }
diff --git a/android/webkit/CacheManager.java b/android/webkit/CacheManager.java
index b839420..fc76029 100644
--- a/android/webkit/CacheManager.java
+++ b/android/webkit/CacheManager.java
@@ -16,13 +16,14 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Map;
 
-
 /**
  * Manages the HTTP cache used by an application's {@link WebView} instances.
  * @deprecated Access to the HTTP cache will be removed in a future release.
@@ -233,6 +234,7 @@
      * @deprecated This method no longer has any effect and always returns {@code null}.
      */
     @Deprecated
+    @Nullable
     public static File getCacheFileBaseDir() {
         return null;
     }
@@ -287,6 +289,7 @@
      * @deprecated This method no longer has any effect and always returns {@code null}.
      */
     @Deprecated
+    @Nullable
     public static CacheResult getCacheFile(String url,
             Map<String, String> headers) {
         return null;
diff --git a/android/webkit/ClientCertRequest.java b/android/webkit/ClientCertRequest.java
index de17534..0fc47f1 100644
--- a/android/webkit/ClientCertRequest.java
+++ b/android/webkit/ClientCertRequest.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 import java.security.Principal;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
@@ -42,14 +44,16 @@
     public ClientCertRequest() { }
 
     /**
-     * Returns the acceptable types of asymmetric keys (can be {@code null}).
+     * Returns the acceptable types of asymmetric keys.
      */
+    @Nullable
     public abstract String[] getKeyTypes();
 
     /**
      * Returns the acceptable certificate issuers for the certificate
-     *            matching the private key (can be {@code null}).
+     *            matching the private key.
      */
+    @Nullable
     public abstract Principal[] getPrincipals();
 
     /**
diff --git a/android/webkit/CookieManager.java b/android/webkit/CookieManager.java
index 8989293..ae6a2fd 100644
--- a/android/webkit/CookieManager.java
+++ b/android/webkit/CookieManager.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.WebAddress;
 
@@ -116,7 +117,8 @@
      *              HTTP response header
      * @param callback a callback to be executed when the cookie has been set
      */
-    public abstract void setCookie(String url, String value, ValueCallback<Boolean> callback);
+    public abstract void setCookie(String url, String value, @Nullable ValueCallback<Boolean>
+            callback);
 
     /**
      * Gets the cookies for the given URL.
@@ -175,7 +177,7 @@
      * method from a thread without a Looper.
      * @param callback a callback which is executed when the session cookies have been removed
      */
-    public abstract void removeSessionCookies(ValueCallback<Boolean> callback);
+    public abstract void removeSessionCookies(@Nullable ValueCallback<Boolean> callback);
 
     /**
      * Removes all cookies.
@@ -197,7 +199,7 @@
      * method from a thread without a Looper.
      * @param callback a callback which is executed when the cookies have been removed
      */
-    public abstract void removeAllCookies(ValueCallback<Boolean> callback);
+    public abstract void removeAllCookies(@Nullable ValueCallback<Boolean> callback);
 
     /**
      * Gets whether there are stored cookies.
diff --git a/android/webkit/FindActionModeCallback.java b/android/webkit/FindActionModeCallback.java
index 71f85d7..e011d51 100644
--- a/android/webkit/FindActionModeCallback.java
+++ b/android/webkit/FindActionModeCallback.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.res.Resources;
@@ -69,7 +70,7 @@
         mActionMode.finish();
     }
 
-    /*
+    /**
      * Place text in the text field so it can be searched for.  Need to press
      * the find next or find previous button to find all of the matches.
      */
@@ -87,10 +88,12 @@
         mMatchesFound = false;
     }
 
-    /*
-     * Set the WebView to search.  Must be non null.
+    /**
+     * Set the WebView to search.
+     *
+     * @param webView an implementation of WebView
      */
-    public void setWebView(WebView webView) {
+    public void setWebView(@NonNull WebView webView) {
         if (null == webView) {
             throw new AssertionError("WebView supplied to "
                     + "FindActionModeCallback cannot be null");
@@ -107,7 +110,7 @@
         }
     }
 
-    /*
+    /**
      * Move the highlight to the next match.
      * @param next If {@code true}, find the next match further down in the document.
      *             If {@code false}, find the previous match, up in the document.
@@ -130,7 +133,7 @@
         updateMatchesString();
     }
 
-    /*
+    /**
      * Highlight all the instances of the string from mEditText in mWebView.
      */
     public void findAll() {
@@ -169,7 +172,7 @@
         }
     }
 
-    /*
+    /**
      * Update the string which tells the user how many matches were found, and
      * which match is currently highlighted.
      */
diff --git a/android/webkit/MimeTypeMap.java b/android/webkit/MimeTypeMap.java
index e172c02..39874e8 100644
--- a/android/webkit/MimeTypeMap.java
+++ b/android/webkit/MimeTypeMap.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.text.TextUtils;
 
 import libcore.net.MimeUtils;
@@ -86,6 +87,7 @@
      * @param extension A file extension without the leading '.'
      * @return The MIME type for the given extension or {@code null} iff there is none.
      */
+    @Nullable
     public String getMimeTypeFromExtension(String extension) {
         return MimeUtils.guessMimeTypeFromExtension(extension);
     }
@@ -111,6 +113,7 @@
      * @param mimeType A MIME type (i.e. text/plain)
      * @return The extension for the given MIME type or {@code null} iff there is none.
      */
+    @Nullable
     public String getExtensionFromMimeType(String mimeType) {
         return MimeUtils.guessExtensionFromMimeType(mimeType);
     }
@@ -125,7 +128,7 @@
      * @param contentDisposition Content-disposition header given by the server.
      * @return The MIME type that should be used for this data.
      */
-    /* package */ String remapGenericMimeType(String mimeType, String url,
+    /* package */ String remapGenericMimeType(@Nullable String mimeType, String url,
             String contentDisposition) {
         // If we have one of "generic" MIME types, try to deduce
         // the right MIME type from the file extension (if any):
diff --git a/android/webkit/Plugin.java b/android/webkit/Plugin.java
index 072e02a..29a5edc 100644
--- a/android/webkit/Plugin.java
+++ b/android/webkit/Plugin.java
@@ -16,12 +16,12 @@
 
 package android.webkit;
 
-import com.android.internal.R;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 
+import com.android.internal.R;
+
 /**
  * Represents a plugin (Java equivalent of the PluginPackageAndroid
  * C++ class in libs/WebKitLib/WebKit/WebCore/plugins/android/)
@@ -32,13 +32,13 @@
  */
 @Deprecated
 public class Plugin {
-    /*
+    /**
      * @hide
      * @deprecated This interface was intended to be used by Gears. Since Gears was
      * deprecated, so is this class.
      */
     public interface PreferencesClickHandler {
-        /*
+        /**
          * @hide
          * @deprecated This interface was intended to be used by Gears. Since Gears was
          * deprecated, so is this class.
diff --git a/android/webkit/ServiceWorkerClient.java b/android/webkit/ServiceWorkerClient.java
index b4964fd..d6e8f36 100644
--- a/android/webkit/ServiceWorkerClient.java
+++ b/android/webkit/ServiceWorkerClient.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 /**
  * Base class for clients to capture Service Worker related callbacks,
  * see {@link ServiceWorkerController} for usage example.
@@ -37,6 +39,7 @@
      *         resource itself.
      * @see WebViewClient#shouldInterceptRequest(WebView, WebResourceRequest)
      */
+    @Nullable
     public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
         return null;
     }
diff --git a/android/webkit/TokenBindingService.java b/android/webkit/TokenBindingService.java
index 43565c2..b37e1b8 100644
--- a/android/webkit/TokenBindingService.java
+++ b/android/webkit/TokenBindingService.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.Uri;
 
@@ -84,31 +86,30 @@
      * The user can pass {@code null} if any algorithm is acceptable.
      *
      * @param origin The origin for the server.
-     * @param algorithm The list of algorithms. Can be {@code null}. An
-     *        IllegalArgumentException is thrown if array is empty.
+     * @param algorithm The list of algorithms. An IllegalArgumentException is thrown if array is
+     *                  empty.
      * @param callback The callback that will be called when key is available.
-     *        Cannot be {@code null}.
      */
     public abstract void getKey(Uri origin,
-                                String[] algorithm,
-                                ValueCallback<TokenBindingKey> callback);
+                                @Nullable String[] algorithm,
+                                @NonNull ValueCallback<TokenBindingKey> callback);
     /**
      * Deletes specified key (for use when associated cookie is cleared).
      *
      * @param origin The origin of the server.
      * @param callback The callback that will be called when key is deleted. The
      *        callback parameter (Boolean) will indicate if operation is
-     *        successful or if failed. The callback can be {@code null}.
+     *        successful or if failed.
      */
     public abstract void deleteKey(Uri origin,
-                                   ValueCallback<Boolean> callback);
+                                   @Nullable ValueCallback<Boolean> callback);
 
      /**
       * Deletes all the keys (for use when cookies are cleared).
       *
       * @param callback The callback that will be called when keys are deleted.
       *        The callback parameter (Boolean) will indicate if operation is
-      *        successful or if failed. The callback can be {@code null}.
+      *        successful or if failed.
       */
-    public abstract void deleteAllKeys(ValueCallback<Boolean> callback);
+    public abstract void deleteAllKeys(@Nullable ValueCallback<Boolean> callback);
 }
diff --git a/android/webkit/URLUtil.java b/android/webkit/URLUtil.java
index c8bfb77..c2f121a 100644
--- a/android/webkit/URLUtil.java
+++ b/android/webkit/URLUtil.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.net.ParseException;
 import android.net.Uri;
 import android.net.WebAddress;
@@ -300,8 +301,8 @@
      */
     public static final String guessFileName(
             String url,
-            String contentDisposition,
-            String mimeType) {
+            @Nullable String contentDisposition,
+            @Nullable String mimeType) {
         String filename = null;
         String extension = null;
 
@@ -388,7 +389,7 @@
             Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
             Pattern.CASE_INSENSITIVE);
 
-    /*
+    /**
      * Parse the Content-Disposition HTTP Header. The format of the header
      * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
      * This header provides a filename for content that is going to be
diff --git a/android/webkit/UrlInterceptHandler.java b/android/webkit/UrlInterceptHandler.java
index aa5c6dc..0a6e51f 100644
--- a/android/webkit/UrlInterceptHandler.java
+++ b/android/webkit/UrlInterceptHandler.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.webkit.CacheManager.CacheResult;
 import android.webkit.PluginData;
 
@@ -35,14 +36,15 @@
      * not interested.
      *
      * @param url URL string.
-     * @param headers The headers associated with the request. May be {@code null}.
+     * @param headers The headers associated with the request.
      * @return The CacheResult containing the surrogate response.
      *
      * @hide
      * @deprecated Do not use, this interface is deprecated.
      */
     @Deprecated
-    public CacheResult service(String url, Map<String, String> headers);
+    @Nullable
+    CacheResult service(String url, @Nullable Map<String, String> headers);
 
     /**
      * Given an URL, returns the PluginData which contains the
@@ -50,12 +52,13 @@
      * not interested.
      *
      * @param url URL string.
-     * @param headers The headers associated with the request. May be {@code null}.
+     * @param headers The headers associated with the request.
      * @return The PluginData containing the surrogate response.
      *
      * @hide
      * @deprecated Do not use, this interface is deprecated.
      */
     @Deprecated
-    public PluginData getPluginData(String url, Map<String, String> headers);
+    @Nullable
+    PluginData getPluginData(String url, @Nullable Map<String, String> headers);
 }
diff --git a/android/webkit/UrlInterceptRegistry.java b/android/webkit/UrlInterceptRegistry.java
index 67af2ad..700d6d9 100644
--- a/android/webkit/UrlInterceptRegistry.java
+++ b/android/webkit/UrlInterceptRegistry.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.webkit.CacheManager.CacheResult;
 import android.webkit.PluginData;
 import android.webkit.UrlInterceptHandler;
@@ -121,6 +122,7 @@
      * deprecated, so is this class.
      */
     @Deprecated
+    @Nullable
     public static synchronized CacheResult getSurrogate(
             String url, Map<String, String> headers) {
         if (urlInterceptDisabled()) {
@@ -149,6 +151,7 @@
      * deprecated, so is this class.
      */
     @Deprecated
+    @Nullable
     public static synchronized PluginData getPluginData(
             String url, Map<String, String> headers) {
         if (urlInterceptDisabled()) {
diff --git a/android/webkit/WebBackForwardList.java b/android/webkit/WebBackForwardList.java
index 3349b06..0c34e3c 100644
--- a/android/webkit/WebBackForwardList.java
+++ b/android/webkit/WebBackForwardList.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 import java.io.Serializable;
 
 /**
@@ -29,6 +31,7 @@
      * empty.
      * @return The current history item.
      */
+    @Nullable
     public abstract WebHistoryItem getCurrentItem();
 
     /**
diff --git a/android/webkit/WebChromeClient.java b/android/webkit/WebChromeClient.java
index 742daa9..4aa1c4a 100644
--- a/android/webkit/WebChromeClient.java
+++ b/android/webkit/WebChromeClient.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -383,6 +384,7 @@
      * @return Bitmap The image to use as a default poster, or {@code null} if no such image is
      * available.
      */
+    @Nullable
     public Bitmap getDefaultVideoPoster() {
         return null;
     }
@@ -394,6 +396,7 @@
      *
      * @return View The View to be displayed whilst the video is loading.
      */
+    @Nullable
     public View getVideoLoadingProgressView() {
         return null;
     }
@@ -452,6 +455,7 @@
          * @return the Uris of selected file(s) or {@code null} if the resultCode indicates
          *         activity canceled or any other error.
          */
+        @Nullable
         public static Uri[] parseResult(int resultCode, Intent data) {
             return WebViewFactory.getProvider().getStatics().parseFileChooserResult(resultCode, data);
         }
@@ -477,14 +481,16 @@
         public abstract boolean isCaptureEnabled();
 
         /**
-         * Returns the title to use for this file selector, or null. If {@code null} a default
-         * title should be used.
+         * Returns the title to use for this file selector. If {@code null} a default title should
+         * be used.
          */
+        @Nullable
         public abstract CharSequence getTitle();
 
         /**
          * The file name of a default selection if specified, or {@code null}.
          */
+        @Nullable
         public abstract String getFilenameHint();
 
         /**
diff --git a/android/webkit/WebHistoryItem.java b/android/webkit/WebHistoryItem.java
index 1591833..74db039 100644
--- a/android/webkit/WebHistoryItem.java
+++ b/android/webkit/WebHistoryItem.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.graphics.Bitmap;
 
@@ -70,6 +71,7 @@
      * Note: The VM ensures 32-bit atomic read/write operations so we don't have
      * to synchronize this method.
      */
+    @Nullable
     public abstract Bitmap getFavicon();
 
     /**
diff --git a/android/webkit/WebMessage.java b/android/webkit/WebMessage.java
index 7fe66dc..bfc00e7 100644
--- a/android/webkit/WebMessage.java
+++ b/android/webkit/WebMessage.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 /**
  * The Java representation of the HTML5 PostMessage event. See
  * https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces
@@ -56,6 +58,7 @@
      * Returns the ports that are sent with the message, or {@code null} if no port
      * is sent.
      */
+    @Nullable
     public WebMessagePort[] getPorts() {
         return mPorts;
     }
diff --git a/android/webkit/WebResourceResponse.java b/android/webkit/WebResourceResponse.java
index 80c43c1..7bc7b07 100644
--- a/android/webkit/WebResourceResponse.java
+++ b/android/webkit/WebResourceResponse.java
@@ -16,12 +16,13 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
 import java.io.InputStream;
 import java.io.StringBufferInputStream;
 import java.util.Map;
 
-import android.annotation.SystemApi;
-
 /**
  * Encapsulates a resource response. Applications can return an instance of this
  * class from {@link WebViewClient#shouldInterceptRequest} to provide a custom
@@ -63,15 +64,15 @@
      * @param encoding the resource response's encoding
      * @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
      *                   Causing a redirect by specifying a 3xx code is not supported.
-     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
-     *                     and not empty.
+     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be
+     *                     non-empty.
      * @param responseHeaders the resource response's headers represented as a mapping of header
      *                        name -> header value.
      * @param data the input stream that provides the resource response's data. Must not be a
      *             StringBufferInputStream.
      */
     public WebResourceResponse(String mimeType, String encoding, int statusCode,
-            String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
+            @NonNull String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
         this(mimeType, encoding, data);
         setStatusCodeAndReasonPhrase(statusCode, reasonPhrase);
         setResponseHeaders(responseHeaders);
@@ -121,10 +122,10 @@
      *
      * @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
      *                   Causing a redirect by specifying a 3xx code is not supported.
-     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
-     *                     and not empty.
+     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be
+     *                     non-empty.
      */
-    public void setStatusCodeAndReasonPhrase(int statusCode, String reasonPhrase) {
+    public void setStatusCodeAndReasonPhrase(int statusCode, @NonNull String reasonPhrase) {
         checkImmutable();
         if (statusCode < 100)
             throw new IllegalArgumentException("statusCode can't be less than 100.");
diff --git a/android/webkit/WebSettings.java b/android/webkit/WebSettings.java
index 22d8561..203de9c 100644
--- a/android/webkit/WebSettings.java
+++ b/android/webkit/WebSettings.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
 
@@ -1238,7 +1239,7 @@
      *
      * @param ua new user-agent string
      */
-    public abstract void setUserAgentString(String ua);
+    public abstract void setUserAgentString(@Nullable String ua);
 
     /**
      * Gets the WebView's user-agent string.
diff --git a/android/webkit/WebSyncManager.java b/android/webkit/WebSyncManager.java
index 801be12..03b94e7 100644
--- a/android/webkit/WebSyncManager.java
+++ b/android/webkit/WebSyncManager.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 
-/*
+/**
  * @deprecated The WebSyncManager no longer does anything.
  */
 @Deprecated
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 202f204..dfc81b2 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2006 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,223 +16,3001 @@
 
 package android.webkit;
 
-import com.android.layoutlib.bridge.MockView;
-
+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 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;
 
 /**
- * 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.
+ * <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>
  *
  */
-public class WebView extends MockView {
+// 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;
 
     /**
-     * Construct a new WebView with a Context object.
-     * @param context A Context object used to access application assets.
+     *  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
      */
     public WebView(Context context) {
         this(context, null);
     }
 
     /**
-     * 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.
+     * 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
      */
     public WebView(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.webViewStyle);
     }
 
     /**
-     * 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.
+     * 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.
      */
-    public WebView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
+    public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
     }
-    
-    // START FAKE PUBLIC METHODS
-    
+
+    /**
+     * 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
     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() {
-        return false;
+        // 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;
     }
 
-    public void savePassword(String host, String username, String password) {
+    /**
+     * 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();
     }
 
+    /**
+     * 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) {
-        return null;
+        checkThread();
+        return mProvider.getHttpAuthUsernamePassword(host, realm);
     }
 
+    /**
+     * 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);
     }
 
-    public void loadData(String data, String mimeType, String encoding) {
+    /**
+     * 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 loadDataWithBaseURL(String baseUrl, String data,
-            String mimeType, String encoding, String failUrl) {
+    /**
+     * 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);
     }
 
+    /**
+     * 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() {
-        return false;
+        checkThread();
+        return mProvider.canGoBack();
     }
 
+    /**
+     * 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() {
-        return false;
+        checkThread();
+        return mProvider.canGoForward();
     }
 
+    /**
+     * 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) {
-        return false;
+        checkThread();
+        return mProvider.canGoBackOrForward(steps);
     }
 
+    /**
+     * 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) {
-        return false;
+        checkThread();
+        return mProvider.pageUp(top);
     }
-    
+
+    /**
+     * 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) {
-        return false;
+        checkThread();
+        return mProvider.pageDown(bottom);
     }
 
+    /**
+     * 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() {
-        return null;
+        checkThread();
+        return mProvider.capturePicture();
     }
 
+    /**
+     * @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() {
-        return 0;
+        checkThread();
+        return mProvider.getScale();
     }
 
+    /**
+     * 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();
     }
 
-    public void requestFocusNodeHref(Message hrefMsg) {
+    /**
+     * 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();
     }
 
+    /**
+     * 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() {
-        return null;
+        checkThread();
+        return mProvider.getUrl();
     }
 
+    /**
+     * 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() {
-        return null;
+        checkThread();
+        return mProvider.getTitle();
     }
 
+    /**
+     * 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() {
-        return null;
+        checkThread();
+        return mProvider.getFavicon();
     }
 
+    /**
+     * 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() {
-        return 0;
+        checkThread();
+        return mProvider.getProgress();
     }
-    
+
+    /**
+     * Gets the height of the HTML content.
+     *
+     * @return the height of the HTML content
+     */
+    @ViewDebug.ExportedProperty(category = "webview")
     public int getContentHeight() {
-        return 0;
+        checkThread();
+        return mProvider.getContentHeight();
     }
 
+    /**
+     * 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();
     }
 
-    public void clearCache() {
+    /**
+     * 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();
     }
 
+    /**
+     * 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) {
-        return null;
+        // TODO: Rewrite this in Java so it is not needed to start up chromium
+        // Could also be deprecated
+        return getFactory().getStatics().findAddress(addr);
     }
 
+    /**
+     * 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);
     }
 
-    public void addJavascriptInterface(Object obj, String interfaceName) {
+    /**
+     * Gets the chrome handler.
+     *
+     * @return the WebChromeClient, or {@code null} if not yet set
+     * @see #setWebChromeClient
+     */
+    @Nullable
+    public WebChromeClient getWebChromeClient() {
+        checkThread();
+        return mProvider.getWebChromeClient();
     }
 
+    /**
+     * 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() {
-        return null;
+        checkThread();
+        return mProvider.getZoomControls();
     }
 
+    /**
+     * 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() {
-        return false;
+        checkThread();
+        return mProvider.zoomIn();
     }
 
+    /**
+     * Performs zoom out in this WebView.
+     *
+     * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
+     */
     public boolean zoomOut() {
-        return false;
+        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());
     }
 }
diff --git a/android/webkit/WebViewClient.java b/android/webkit/WebViewClient.java
index af7026d..c5b64eb 100644
--- a/android/webkit/WebViewClient.java
+++ b/android/webkit/WebViewClient.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.net.http.SslError;
 import android.os.Message;
@@ -167,6 +168,7 @@
      *             shouldInterceptRequest(WebView, WebResourceRequest)} instead.
      */
     @Deprecated
+    @Nullable
     public WebResourceResponse shouldInterceptRequest(WebView view,
             String url) {
         return null;
@@ -191,6 +193,7 @@
      *         response information or {@code null} if the WebView should load the
      *         resource itself.
      */
+    @Nullable
     public WebResourceResponse shouldInterceptRequest(WebView view,
             WebResourceRequest request) {
         return shouldInterceptRequest(view, request.getUrl().toString());
@@ -496,7 +499,7 @@
      * @param args Authenticator specific arguments used to log in the user.
      */
     public void onReceivedLoginRequest(WebView view, String realm,
-            String account, String args) {
+            @Nullable String account, String args) {
     }
 
     /**
diff --git a/android/webkit/WebViewDatabase.java b/android/webkit/WebViewDatabase.java
index de75d5d..f6166c5 100644
--- a/android/webkit/WebViewDatabase.java
+++ b/android/webkit/WebViewDatabase.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.content.Context;
 
 /**
@@ -135,6 +136,7 @@
      * @see #hasHttpAuthUsernamePassword
      * @see #clearHttpAuthUsernamePassword
      */
+    @Nullable
     public abstract String[] getHttpAuthUsernamePassword(String host, String realm);
 
     /**
diff --git a/android/webkit/WebViewFactory.java b/android/webkit/WebViewFactory.java
index 7c4154f..95cb454 100644
--- a/android/webkit/WebViewFactory.java
+++ b/android/webkit/WebViewFactory.java
@@ -51,9 +51,6 @@
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
-    private static final String NULL_WEBVIEW_FACTORY =
-            "com.android.webview.nullwebview.NullWebViewFactoryProvider";
-
     public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
             "persist.sys.webview.vmsize";
 
@@ -66,6 +63,7 @@
     private static WebViewFactoryProvider sProviderInstance;
     private static final Object sProviderLock = new Object();
     private static PackageInfo sPackageInfo;
+    private static Boolean sWebViewSupported;
 
     // Error codes for loadWebViewNativeLibraryFromPackage
     public static final int LIBLOAD_SUCCESS = 0;
@@ -105,6 +103,16 @@
         public MissingWebViewPackageException(Exception e) { super(e); }
     }
 
+    private static boolean isWebViewSupported() {
+        // No lock; this is a benign race as Boolean's state is final and the PackageManager call
+        // will always return the same value.
+        if (sWebViewSupported == null) {
+            sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
+        }
+        return sWebViewSupported;
+    }
+
     /**
      * @hide
      */
@@ -135,6 +143,10 @@
      */
     public static int loadWebViewNativeLibraryFromPackage(String packageName,
                                                           ClassLoader clazzLoader) {
+        if (!isWebViewSupported()) {
+            return LIBLOAD_WRONG_PACKAGE_NAME;
+        }
+
         WebViewProviderResponse response = null;
         try {
             response = getUpdateService().waitForAndGetProvider();
@@ -188,6 +200,11 @@
                         "For security reasons, WebView is not allowed in privileged processes");
             }
 
+            if (!isWebViewSupported()) {
+                // Device doesn't support WebView; don't try to load it, just throw.
+                throw new UnsupportedOperationException();
+            }
+
             StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
             try {
@@ -410,15 +427,6 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
         } catch (MissingWebViewPackageException e) {
-            // If the package doesn't exist, then try loading the null WebView instead.
-            // If that succeeds, then this is a device without WebView support; if it fails then
-            // swallow the failure, complain that the real WebView is missing and rethrow the
-            // original exception.
-            try {
-                return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
-            } catch (ClassNotFoundException e2) {
-                // Ignore.
-            }
             Log.e(LOGTAG, "Chromium WebView package does not exist", e);
             throw new AndroidRuntimeException(e);
         }
@@ -446,13 +454,13 @@
         // waiting on relro creation.
         if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
             if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
-            WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths);
+            WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]);
             numRelros++;
         }
 
         if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
             if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
-            WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths);
+            WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]);
             numRelros++;
         }
         return numRelros;
@@ -463,7 +471,7 @@
      */
     public static int onWebViewProviderChanged(PackageInfo packageInfo) {
         String[] nativeLibs = null;
-        String originalSourceDir = packageInfo.applicationInfo.sourceDir;
+        ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
         try {
             fixupStubApplicationInfo(packageInfo.applicationInfo,
                                      AppGlobals.getInitialApplication().getPackageManager());
@@ -474,7 +482,7 @@
             Log.e(LOGTAG, "error preparing webview native library", t);
         }
 
-        WebViewZygote.onWebViewProviderChanged(packageInfo, originalSourceDir);
+        WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
 
         return prepareWebViewInSystemServer(nativeLibs);
     }
@@ -483,6 +491,15 @@
 
     /** @hide */
     public static IWebViewUpdateService getUpdateService() {
+        if (isWebViewSupported()) {
+            return getUpdateServiceUnchecked();
+        } else {
+            return null;
+        }
+    }
+
+    /** @hide */
+    static IWebViewUpdateService getUpdateServiceUnchecked() {
         return IWebViewUpdateService.Stub.asInterface(
                 ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
     }
diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java
index 6f9e8ec..fa1a390 100644
--- a/android/webkit/WebViewLibraryLoader.java
+++ b/android/webkit/WebViewLibraryLoader.java
@@ -62,25 +62,23 @@
             boolean result = false;
             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
             try {
-                if (args.length != 2 || args[0] == null || args[1] == null) {
+                if (args.length != 1 || args[0] == null) {
                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
                     return;
                 }
-                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), "
-                        + " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
+                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), lib: " + args[0]);
                 if (!sAddressSpaceReserved) {
                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
                     return;
                 }
-                result = nativeCreateRelroFile(args[0] /* path32 */,
-                                               args[1] /* path64 */,
-                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
-                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
+                result = nativeCreateRelroFile(args[0] /* path */,
+                                               is64Bit ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
+                                                         CHROMIUM_WEBVIEW_NATIVE_RELRO_32);
                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
             } finally {
                 // We must do our best to always notify the update service, even if something fails.
                 try {
-                    WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
+                    WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
                 } catch (RemoteException e) {
                     Log.e(LOGTAG, "error notifying update service", e);
                 }
@@ -96,7 +94,7 @@
     /**
      * Create a single relro file by invoking an isolated process that to do the actual work.
      */
-    static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
+    static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) {
         final String abi =
                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
 
@@ -114,13 +112,12 @@
         };
 
         try {
-            if (nativeLibraryPaths == null
-                    || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
+            if (nativeLibraryPath == null) {
                 throw new IllegalArgumentException(
                         "Native library paths to the WebView RelRo process must not be null!");
             }
             int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
-                    RelroFileCreator.class.getName(), nativeLibraryPaths,
+                    RelroFileCreator.class.getName(), new String[] { nativeLibraryPath },
                     "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
             if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
         } catch (Throwable t) {
@@ -217,8 +214,9 @@
 
         final String libraryFileName =
                 WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo);
-        int result = nativeLoadWithRelroFile(libraryFileName, CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
-                                             CHROMIUM_WEBVIEW_NATIVE_RELRO_64, clazzLoader);
+        String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
+                                                              CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
+        int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
         if (result != WebViewFactory.LIBLOAD_SUCCESS) {
             Log.w(LOGTAG, "failed to load with relro file, proceeding without");
         } else if (DEBUG) {
@@ -313,8 +311,6 @@
     }
 
     static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
-    static native boolean nativeCreateRelroFile(String lib32, String lib64,
-                                                        String relro32, String relro64);
-    static native int nativeLoadWithRelroFile(String lib, String relro32, String relro64,
-                                                      ClassLoader clazzLoader);
+    static native boolean nativeCreateRelroFile(String lib, String relro);
+    static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
 }
diff --git a/android/webkit/WebViewUpdateService.java b/android/webkit/WebViewUpdateService.java
index 2f7d685..629891c 100644
--- a/android/webkit/WebViewUpdateService.java
+++ b/android/webkit/WebViewUpdateService.java
@@ -31,8 +31,12 @@
      * Fetch all packages that could potentially implement WebView.
      */
     public static WebViewProviderInfo[] getAllWebViewPackages() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return new WebViewProviderInfo[0];
+        }
         try {
-            return getUpdateService().getAllWebViewPackages();
+            return service.getAllWebViewPackages();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -42,8 +46,12 @@
      * Fetch all packages that could potentially implement WebView and are currently valid.
      */
     public static WebViewProviderInfo[] getValidWebViewPackages() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return new WebViewProviderInfo[0];
+        }
         try {
-            return getUpdateService().getValidWebViewPackages();
+            return service.getValidWebViewPackages();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -53,8 +61,12 @@
      * Used by DevelopmentSetting to get the name of the WebView provider currently in use.
      */
     public static String getCurrentWebViewPackageName() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return null;
+        }
         try {
-            return getUpdateService().getCurrentWebViewPackageName();
+            return service.getCurrentWebViewPackageName();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/android/webkit/WebViewZygote.java b/android/webkit/WebViewZygote.java
index 6e65c7a..db60ad8 100644
--- a/android/webkit/WebViewZygote.java
+++ b/android/webkit/WebViewZygote.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.os.Build;
 import android.os.SystemService;
@@ -67,11 +68,11 @@
     private static PackageInfo sPackage;
 
     /**
-     * Cache key for the selected WebView package's classloader. This is set from
+     * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
      * #onWebViewProviderChanged().
      */
     @GuardedBy("sLock")
-    private static String sPackageCacheKey;
+    private static ApplicationInfo sPackageOriginalAppInfo;
 
     /**
      * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
@@ -125,10 +126,11 @@
         }
     }
 
-    public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) {
+    public static void onWebViewProviderChanged(PackageInfo packageInfo,
+                                                ApplicationInfo originalAppInfo) {
         synchronized (sLock) {
             sPackage = packageInfo;
-            sPackageCacheKey = cacheKey;
+            sPackageOriginalAppInfo = originalAppInfo;
 
             // If multi-process is not enabled, then do not start the zygote service.
             if (!sMultiprocessEnabled) {
@@ -217,10 +219,17 @@
             final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
                     TextUtils.join(File.pathSeparator, zipPaths);
 
+            // In the case where the ApplicationInfo has been modified by the stub WebView,
+            // we need to use the original ApplicationInfo to determine what the original classpath
+            // would have been to use as a cache key.
+            LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
+            final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
+                    TextUtils.join(File.pathSeparator, zipPaths);
+
             ZygoteProcess.waitForConnectionToZygote(WEBVIEW_ZYGOTE_SOCKET);
 
             Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
-            sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
+            sZygote.preloadPackageForAbi(zip, librarySearchPath, cacheKey,
                                          Build.SUPPORTED_ABIS[0]);
         } catch (Exception e) {
             Log.e(LOGTAG, "Error connecting to " + serviceName, e);
diff --git a/android/widget/EditTextBackspacePerfTest.java b/android/widget/EditTextBackspacePerfTest.java
index 40b56f4..d219d3a 100644
--- a/android/widget/EditTextBackspacePerfTest.java
+++ b/android/widget/EditTextBackspacePerfTest.java
@@ -16,31 +16,26 @@
 
 package android.widget;
 
-import android.app.Activity;
-import android.os.Bundle;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
 import android.text.Selection;
 import android.view.KeyEvent;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
 
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/android/widget/EditTextCursorMovementPerfTest.java b/android/widget/EditTextCursorMovementPerfTest.java
index b100acb..b6cf7d3 100644
--- a/android/widget/EditTextCursorMovementPerfTest.java
+++ b/android/widget/EditTextCursorMovementPerfTest.java
@@ -16,31 +16,26 @@
 
 package android.widget;
 
-import android.app.Activity;
-import android.os.Bundle;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
 import android.text.Selection;
 import android.view.KeyEvent;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
 
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index 0f61724..afd1188 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -119,6 +119,7 @@
 import com.android.internal.util.GrowingArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.EditableInputConnection;
+import com.android.internal.widget.Magnifier;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -138,6 +139,9 @@
 public class Editor {
     private static final String TAG = "Editor";
     private static final boolean DEBUG_UNDO = false;
+    // Specifies whether to use or not the magnifier when pressing the insertion or selection
+    // handles.
+    private static final boolean FLAG_USE_MAGNIFIER = true;
 
     static final int BLINK = 500;
     private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
@@ -161,6 +165,17 @@
     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;
+    @IntDef({MagnifierHandleTrigger.SELECTION_START,
+            MagnifierHandleTrigger.SELECTION_END,
+            MagnifierHandleTrigger.INSERTION})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface MagnifierHandleTrigger {
+        int INSERTION = 0;
+        int SELECTION_START = 1;
+        int SELECTION_END = 2;
+    }
+
     // Each Editor manages its own undo stack.
     private final UndoManager mUndoManager = new UndoManager();
     private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
@@ -179,6 +194,8 @@
 
     private final boolean mHapticTextHandleEnabled;
 
+    private final Magnifier mMagnifier;
+
     // Used to highlight a word when it is corrected by the IME
     private CorrectionHighlighter mCorrectionHighlighter;
 
@@ -250,7 +267,7 @@
     SuggestionRangeSpan mSuggestionRangeSpan;
     private Runnable mShowSuggestionRunnable;
 
-    Drawable mCursorDrawable = null;
+    Drawable mDrawableForCursor = null;
 
     private Drawable mSelectHandleLeft;
     private Drawable mSelectHandleRight;
@@ -325,6 +342,8 @@
         mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this);
         mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
+
+        mMagnifier = FLAG_USE_MAGNIFIER ? new Magnifier(mTextView) : null;
     }
 
     ParcelableParcel saveInstanceState() {
@@ -1678,7 +1697,7 @@
             mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
         }
 
-        if (highlight != null && selectionStart == selectionEnd && mCursorDrawable != null) {
+        if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) {
             drawCursor(canvas, cursorOffsetVertical);
             // Rely on the drawable entirely, do not draw the cursor line.
             // Has to be done after the IMM related code above which relies on the highlight.
@@ -1873,8 +1892,8 @@
     private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
         final boolean translate = cursorOffsetVertical != 0;
         if (translate) canvas.translate(0, cursorOffsetVertical);
-        if (mCursorDrawable != null) {
-            mCursorDrawable.draw(canvas);
+        if (mDrawableForCursor != null) {
+            mDrawableForCursor.draw(canvas);
         }
         if (translate) canvas.translate(0, -cursorOffsetVertical);
     }
@@ -1933,7 +1952,7 @@
 
     void updateCursorPosition() {
         if (mTextView.mCursorDrawableRes == 0) {
-            mCursorDrawable = null;
+            mDrawableForCursor = null;
             return;
         }
 
@@ -2314,17 +2333,17 @@
     @VisibleForTesting
     @Nullable
     public Drawable getCursorDrawable() {
-        return mCursorDrawable;
+        return mDrawableForCursor;
     }
 
     private void updateCursorPosition(int top, int bottom, float horizontal) {
-        if (mCursorDrawable == null) {
-            mCursorDrawable = mTextView.getContext().getDrawable(
+        if (mDrawableForCursor == null) {
+            mDrawableForCursor = mTextView.getContext().getDrawable(
                     mTextView.mCursorDrawableRes);
         }
-        final int left = clampHorizontalPosition(mCursorDrawable, horizontal);
-        final int width = mCursorDrawable.getIntrinsicWidth();
-        mCursorDrawable.setBounds(left, top - mTempRect.top, left + width,
+        final int left = clampHorizontalPosition(mDrawableForCursor, horizontal);
+        final int width = mDrawableForCursor.getIntrinsicWidth();
+        mDrawableForCursor.setBounds(left, top - mTempRect.top, left + width,
                 bottom + mTempRect.bottom);
     }
 
@@ -4353,6 +4372,9 @@
 
         protected abstract void updatePosition(float x, float y, boolean fromTouchScreen);
 
+        @MagnifierHandleTrigger
+        protected abstract int getMagnifierHandleTrigger();
+
         protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
             return layout.isRtlCharAt(offset);
         }
@@ -4490,6 +4512,53 @@
             return 0;
         }
 
+        protected final void showMagnifier() {
+            if (mMagnifier == null) {
+                return;
+            }
+
+            final int trigger = getMagnifierHandleTrigger();
+            final int offset;
+            switch (trigger) {
+                case MagnifierHandleTrigger.INSERTION: // Fall through.
+                case MagnifierHandleTrigger.SELECTION_START:
+                    offset = mTextView.getSelectionStart();
+                    break;
+                case MagnifierHandleTrigger.SELECTION_END:
+                    offset = mTextView.getSelectionEnd();
+                    break;
+                default:
+                    offset = -1;
+                    break;
+            }
+
+            if (offset == -1) {
+                dismissMagnifier();
+            }
+
+            final Layout layout = mTextView.getLayout();
+            final int lineNumber = layout.getLineForOffset(offset);
+            // Horizontally snap to character offset.
+            final float xPosInView = getHorizontal(mTextView.getLayout(), offset);
+            // 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];
+
+            mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+        }
+
+        protected final void dismissMagnifier() {
+            if (mMagnifier != null) {
+                mMagnifier.dismiss();
+            }
+        }
+
         @Override
         public boolean onTouchEvent(MotionEvent ev) {
             updateFloatingToolbarVisibility(ev);
@@ -4542,10 +4611,7 @@
 
                 case MotionEvent.ACTION_UP:
                     filterOnTouchUp(ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
-                    mIsDragging = false;
-                    updateDrawable();
-                    break;
-
+                    // Fall through.
                 case MotionEvent.ACTION_CANCEL:
                     mIsDragging = false;
                     updateDrawable();
@@ -4646,9 +4712,9 @@
         @Override
         protected int getCursorOffset() {
             int offset = super.getCursorOffset();
-            if (mCursorDrawable != null) {
-                mCursorDrawable.getPadding(mTempRect);
-                offset += (mCursorDrawable.getIntrinsicWidth()
+            if (mDrawableForCursor != null) {
+                mDrawableForCursor.getPadding(mTempRect);
+                offset += (mDrawableForCursor.getIntrinsicWidth()
                            - mTempRect.left - mTempRect.right) / 2;
             }
             return offset;
@@ -4656,9 +4722,9 @@
 
         @Override
         int getCursorHorizontalPosition(Layout layout, int offset) {
-            if (mCursorDrawable != null) {
+            if (mDrawableForCursor != null) {
                 final float horizontal = getHorizontal(layout, offset);
-                return clampHorizontalPosition(mCursorDrawable, horizontal) + mTempRect.left;
+                return clampHorizontalPosition(mDrawableForCursor, horizontal) + mTempRect.left;
             }
             return super.getCursorHorizontalPosition(layout, offset);
         }
@@ -4671,6 +4737,11 @@
                 case MotionEvent.ACTION_DOWN:
                     mDownPositionX = ev.getRawX();
                     mDownPositionY = ev.getRawY();
+                    showMagnifier();
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    showMagnifier();
                     break;
 
                 case MotionEvent.ACTION_UP:
@@ -4696,11 +4767,10 @@
                             mTextActionMode.invalidateContentRect();
                         }
                     }
-                    hideAfterDelay();
-                    break;
-
+                    // Fall through.
                 case MotionEvent.ACTION_CANCEL:
                     hideAfterDelay();
+                    dismissMagnifier();
                     break;
 
                 default:
@@ -4751,6 +4821,12 @@
             super.onDetached();
             removeHiderCallback();
         }
+
+        @Override
+        @MagnifierHandleTrigger
+        protected int getMagnifierHandleTrigger() {
+            return MagnifierHandleTrigger.INSERTION;
+        }
     }
 
     @Retention(RetentionPolicy.SOURCE)
@@ -5009,12 +5085,26 @@
         @Override
         public boolean onTouchEvent(MotionEvent event) {
             boolean superResult = super.onTouchEvent(event);
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                // Reset the touch word offset and x value when the user
-                // re-engages the handle.
-                mTouchWordDelta = 0.0f;
-                mPrevX = UNSET_X_VALUE;
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    // Reset the touch word offset and x value when the user
+                    // re-engages the handle.
+                    mTouchWordDelta = 0.0f;
+                    mPrevX = UNSET_X_VALUE;
+                    showMagnifier();
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    showMagnifier();
+                    break;
+
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    dismissMagnifier();
+                    break;
             }
+
             return superResult;
         }
 
@@ -5110,6 +5200,13 @@
                 return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset;
             }
         }
+
+        @MagnifierHandleTrigger
+        protected int getMagnifierHandleTrigger() {
+            return isStartHandle()
+                    ? MagnifierHandleTrigger.SELECTION_START
+                    : MagnifierHandleTrigger.SELECTION_END;
+        }
     }
 
     private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index bf25915..23ebadb 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -2296,8 +2296,8 @@
     }
 
     /** @hide */
-    protected final void detachFromAnchor() {
-        final View anchor = mAnchor != null ? mAnchor.get() : null;
+    protected void detachFromAnchor() {
+        final View anchor = getAnchor();
         if (anchor != null) {
             final ViewTreeObserver vto = anchor.getViewTreeObserver();
             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
@@ -2316,7 +2316,7 @@
     }
 
     /** @hide */
-    protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+    protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
         detachFromAnchor();
 
         final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2339,6 +2339,11 @@
         mAnchoredGravity = gravity;
     }
 
+    /** @hide */
+    protected @Nullable View getAnchor() {
+        return mAnchor != null ? mAnchor.get() : null;
+    }
+
     private void alignToAnchor() {
         final View anchor = mAnchor != null ? mAnchor.get() : null;
         if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index bc85fad..1b26f8e 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -16,9 +16,11 @@
 
 package android.widget;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
 import android.annotation.ColorInt;
 import android.annotation.DimenRes;
-import android.app.ActivityManager.StackId;
+import android.annotation.NonNull;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.Application;
@@ -107,9 +109,9 @@
     // The unique identifiers for each custom {@link Action}.
     private static final int SET_ON_CLICK_PENDING_INTENT_TAG = 1;
     private static final int REFLECTION_ACTION_TAG = 2;
-    private static final int SET_DRAWABLE_PARAMETERS_TAG = 3;
+    private static final int SET_DRAWABLE_TINT_TAG = 3;
     private static final int VIEW_GROUP_ACTION_ADD_TAG = 4;
-    private static final int SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG = 5;
+    private static final int VIEW_CONTENT_NAVIGATION_TAG = 5;
     private static final int SET_EMPTY_VIEW_ACTION_TAG = 6;
     private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7;
     private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8;
@@ -120,7 +122,6 @@
     private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13;
     private static final int VIEW_PADDING_ACTION_TAG = 14;
     private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15;
-    private static final int TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG = 17;
     private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18;
     private static final int LAYOUT_PARAM_ACTION_TAG = 19;
     private static final int OVERRIDE_TEXT_COLORS_TAG = 20;
@@ -324,11 +325,11 @@
 
         public boolean onClickHandler(View view, PendingIntent pendingIntent,
                 Intent fillInIntent) {
-            return onClickHandler(view, pendingIntent, fillInIntent, StackId.INVALID_STACK_ID);
+            return onClickHandler(view, pendingIntent, fillInIntent, WINDOWING_MODE_UNDEFINED);
         }
 
         public boolean onClickHandler(View view, PendingIntent pendingIntent,
-                Intent fillInIntent, int launchStackId) {
+                Intent fillInIntent, int windowingMode) {
             try {
                 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
                 Context context = view.getContext();
@@ -339,8 +340,8 @@
                     opts = ActivityOptions.makeBasic();
                 }
 
-                if (launchStackId != StackId.INVALID_STACK_ID) {
-                    opts.setLaunchStackId(launchStackId);
+                if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+                    opts.setLaunchWindowingMode(windowingMode);
                 }
                 context.startIntentSender(
                         pendingIntent.getIntentSender(), fillInIntent,
@@ -388,10 +389,10 @@
             return MERGE_REPLACE;
         }
 
-        public abstract String getActionName();
+        public abstract int getActionTag();
 
         public String getUniqueKey() {
-            return (getActionName() + viewId);
+            return (getActionTag() + "_" + viewId);
         }
 
         /**
@@ -423,8 +424,8 @@
      */
     private static abstract class RuntimeAction extends Action {
         @Override
-        public final String getActionName() {
-            return "RuntimeAction";
+        public final int getActionTag() {
+            return 0;
         }
 
         @Override
@@ -513,7 +514,6 @@
     }
 
     private class SetEmptyView extends Action {
-        int viewId;
         int emptyViewId;
 
         SetEmptyView(int viewId, int emptyViewId) {
@@ -527,7 +527,6 @@
         }
 
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(SET_EMPTY_VIEW_ACTION_TAG);
             out.writeInt(this.viewId);
             out.writeInt(this.emptyViewId);
         }
@@ -545,8 +544,9 @@
             adapterView.setEmptyView(emptyView);
         }
 
-        public String getActionName() {
-            return "SetEmptyView";
+        @Override
+        public int getActionTag() {
+            return SET_EMPTY_VIEW_ACTION_TAG;
         }
     }
 
@@ -558,13 +558,12 @@
 
         public SetOnClickFillInIntent(Parcel parcel) {
             viewId = parcel.readInt();
-            fillInIntent = Intent.CREATOR.createFromParcel(parcel);
+            fillInIntent = parcel.readTypedObject(Intent.CREATOR);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_ON_CLICK_FILL_IN_INTENT_TAG);
             dest.writeInt(viewId);
-            fillInIntent.writeToParcel(dest, 0 /* no flags */);
+            dest.writeTypedObject(fillInIntent, 0 /* no flags */);
         }
 
         @Override
@@ -623,8 +622,9 @@
             }
         }
 
-        public String getActionName() {
-            return "SetOnClickFillInIntent";
+        @Override
+        public int getActionTag() {
+            return SET_ON_CLICK_FILL_IN_INTENT_TAG;
         }
 
         Intent fillInIntent;
@@ -642,9 +642,8 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_PENDING_INTENT_TEMPLATE_TAG);
             dest.writeInt(viewId);
-            pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
+            PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest);
         }
 
         @Override
@@ -698,8 +697,9 @@
             }
         }
 
-        public String getActionName() {
-            return "SetPendingIntentTemplate";
+        @Override
+        public int getActionTag() {
+            return SET_PENDING_INTENT_TEMPLATE_TAG;
         }
 
         PendingIntent pendingIntentTemplate;
@@ -715,30 +715,13 @@
         public SetRemoteViewsAdapterList(Parcel parcel) {
             viewId = parcel.readInt();
             viewTypeCount = parcel.readInt();
-            int count = parcel.readInt();
-            list = new ArrayList<RemoteViews>();
-
-            for (int i = 0; i < count; i++) {
-                RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
-                list.add(rv);
-            }
+            list = parcel.createTypedArrayList(RemoteViews.CREATOR);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_REMOTE_VIEW_ADAPTER_LIST_TAG);
             dest.writeInt(viewId);
             dest.writeInt(viewTypeCount);
-
-            if (list == null || list.size() == 0) {
-                dest.writeInt(0);
-            } else {
-                int count = list.size();
-                dest.writeInt(count);
-                for (int i = 0; i < count; i++) {
-                    RemoteViews rv = list.get(i);
-                    rv.writeToParcel(dest, flags);
-                }
-            }
+            dest.writeTypedList(list, flags);
         }
 
         @Override
@@ -778,8 +761,9 @@
             }
         }
 
-        public String getActionName() {
-            return "SetRemoteViewsAdapterList";
+        @Override
+        public int getActionTag() {
+            return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
         }
 
         int viewTypeCount;
@@ -794,13 +778,12 @@
 
         public SetRemoteViewsAdapterIntent(Parcel parcel) {
             viewId = parcel.readInt();
-            intent = Intent.CREATOR.createFromParcel(parcel);
+            intent = parcel.readTypedObject(Intent.CREATOR);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_REMOTE_VIEW_ADAPTER_INTENT_TAG);
             dest.writeInt(viewId);
-            intent.writeToParcel(dest, flags);
+            dest.writeTypedObject(intent, flags);
         }
 
         @Override
@@ -844,8 +827,9 @@
             return copy;
         }
 
-        public String getActionName() {
-            return "SetRemoteViewsAdapterIntent";
+        @Override
+        public int getActionTag() {
+            return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
         }
 
         Intent intent;
@@ -865,22 +849,12 @@
 
         public SetOnClickPendingIntent(Parcel parcel) {
             viewId = parcel.readInt();
-
-            // We check a flag to determine if the parcel contains a PendingIntent.
-            if (parcel.readInt() != 0) {
-                pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
-            }
+            pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_ON_CLICK_PENDING_INTENT_TAG);
             dest.writeInt(viewId);
-
-            // We use a flag to indicate whether the parcel contains a valid object.
-            dest.writeInt(pendingIntent != null ? 1 : 0);
-            if (pendingIntent != null) {
-                pendingIntent.writeToParcel(dest, 0 /* no flags */);
-            }
+            PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, dest);
         }
 
         @Override
@@ -921,8 +895,9 @@
             target.setOnClickListener(listener);
         }
 
-        public String getActionName() {
-            return "SetOnClickPendingIntent";
+        @Override
+        public int getActionTag() {
+            return SET_ON_CLICK_PENDING_INTENT_TAG;
         }
 
         PendingIntent pendingIntent;
@@ -1011,55 +986,37 @@
     }
 
     /**
-     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+     * Equivalent to calling
      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
-     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
+     * on the {@link Drawable} of a given view.
      * <p>
-     * These operations will be performed on the {@link Drawable} returned by the
+     * The operation will be performed on the {@link Drawable} returned by the
      * target {@link View#getBackground()} by default.  If targetBackground is false,
      * we assume the target is an {@link ImageView} and try applying the operations
      * to {@link ImageView#getDrawable()}.
      * <p>
-     * You can omit specific calls by marking their values with null or -1.
      */
-    private class SetDrawableParameters extends Action {
-        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
-                int colorFilter, PorterDuff.Mode mode, int level) {
+    private class SetDrawableTint extends Action {
+        SetDrawableTint(int id, boolean targetBackground,
+                int colorFilter, @NonNull PorterDuff.Mode mode) {
             this.viewId = id;
             this.targetBackground = targetBackground;
-            this.alpha = alpha;
             this.colorFilter = colorFilter;
             this.filterMode = mode;
-            this.level = level;
         }
 
-        public SetDrawableParameters(Parcel parcel) {
+        SetDrawableTint(Parcel parcel) {
             viewId = parcel.readInt();
             targetBackground = parcel.readInt() != 0;
-            alpha = parcel.readInt();
             colorFilter = parcel.readInt();
-            boolean hasMode = parcel.readInt() != 0;
-            if (hasMode) {
-                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
-            } else {
-                filterMode = null;
-            }
-            level = parcel.readInt();
+            filterMode = PorterDuff.intToMode(parcel.readInt());
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_DRAWABLE_PARAMETERS_TAG);
             dest.writeInt(viewId);
             dest.writeInt(targetBackground ? 1 : 0);
-            dest.writeInt(alpha);
             dest.writeInt(colorFilter);
-            if (filterMode != null) {
-                dest.writeInt(1);
-                dest.writeString(filterMode.toString());
-            } else {
-                dest.writeInt(0);
-            }
-            dest.writeInt(level);
+            dest.writeInt(PorterDuff.modeToInt(filterMode));
         }
 
         @Override
@@ -1077,47 +1034,36 @@
             }
 
             if (targetDrawable != null) {
-                // Perform modifications only if values are set correctly
-                if (alpha != -1) {
-                    targetDrawable.mutate().setAlpha(alpha);
-                }
-                if (filterMode != null) {
-                    targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
-                }
-                if (level != -1) {
-                    targetDrawable.mutate().setLevel(level);
-                }
+                targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
             }
         }
 
-        public String getActionName() {
-            return "SetDrawableParameters";
+        @Override
+        public int getActionTag() {
+            return SET_DRAWABLE_TINT_TAG;
         }
 
         boolean targetBackground;
-        int alpha;
         int colorFilter;
         PorterDuff.Mode filterMode;
-        int level;
     }
 
-    private final class ReflectionActionWithoutParams extends Action {
-        final String methodName;
+    private final class ViewContentNavigation extends Action {
+        final boolean mNext;
 
-        ReflectionActionWithoutParams(int viewId, String methodName) {
+        ViewContentNavigation(int viewId, boolean next) {
             this.viewId = viewId;
-            this.methodName = methodName;
+            this.mNext = next;
         }
 
-        ReflectionActionWithoutParams(Parcel in) {
+        ViewContentNavigation(Parcel in) {
             this.viewId = in.readInt();
-            this.methodName = in.readString();
+            this.mNext = in.readBoolean();
         }
 
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG);
             out.writeInt(this.viewId);
-            out.writeString(this.methodName);
+            out.writeBoolean(this.mNext);
         }
 
         @Override
@@ -1126,23 +1072,20 @@
             if (view == null) return;
 
             try {
-                getMethod(view, this.methodName, null, false /* async */).invoke(view);
+                getMethod(view,
+                        mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view);
             } catch (Throwable ex) {
                 throw new ActionException(ex);
             }
         }
 
         public int mergeBehavior() {
-            // we don't need to build up showNext or showPrevious calls
-            if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
-                return MERGE_IGNORE;
-            } else {
-                return MERGE_REPLACE;
-            }
+            return MERGE_IGNORE;
         }
 
-        public String getActionName() {
-            return "ReflectionActionWithoutParams";
+        @Override
+        public int getActionTag() {
+            return VIEW_CONTENT_NAVIGATION_TAG;
         }
     }
 
@@ -1156,12 +1099,7 @@
         }
 
         public BitmapCache(Parcel source) {
-            int count = source.readInt();
-            mBitmaps = new ArrayList<>(count);
-            for (int i = 0; i < count; i++) {
-                Bitmap b = Bitmap.CREATOR.createFromParcel(source);
-                mBitmaps.add(b);
-            }
+            mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
         }
 
         public int getBitmapId(Bitmap b) {
@@ -1187,11 +1125,7 @@
         }
 
         public void writeBitmapsToParcel(Parcel dest, int flags) {
-            int count = mBitmaps.size();
-            dest.writeInt(count);
-            for (int i = 0; i < count; i++) {
-                mBitmaps.get(i).writeToParcel(dest, flags);
-            }
+            dest.writeTypedList(mBitmaps, flags);
         }
 
         public int getBitmapMemory() {
@@ -1227,7 +1161,6 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(BITMAP_REFLECTION_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeString(methodName);
             dest.writeInt(bitmapId);
@@ -1246,8 +1179,9 @@
             bitmapId = bitmapCache.getBitmapId(bitmap);
         }
 
-        public String getActionName() {
-            return "BitmapReflectionAction";
+        @Override
+        public int getActionTag() {
+            return BITMAP_REFLECTION_ACTION_TAG;
         }
     }
 
@@ -1299,7 +1233,7 @@
             // written to the parcel.
             switch (this.type) {
                 case BOOLEAN:
-                    this.value = in.readInt() != 0;
+                    this.value = in.readBoolean();
                     break;
                 case BYTE:
                     this.value = in.readByte();
@@ -1329,39 +1263,28 @@
                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
                     break;
                 case URI:
-                    if (in.readInt() != 0) {
-                        this.value = Uri.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Uri.CREATOR);
                     break;
                 case BITMAP:
-                    if (in.readInt() != 0) {
-                        this.value = Bitmap.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Bitmap.CREATOR);
                     break;
                 case BUNDLE:
                     this.value = in.readBundle();
                     break;
                 case INTENT:
-                    if (in.readInt() != 0) {
-                        this.value = Intent.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Intent.CREATOR);
                     break;
                 case COLOR_STATE_LIST:
-                    if (in.readInt() != 0) {
-                        this.value = ColorStateList.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(ColorStateList.CREATOR);
                     break;
                 case ICON:
-                    if (in.readInt() != 0) {
-                        this.value = Icon.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Icon.CREATOR);
                 default:
                     break;
             }
         }
 
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(REFLECTION_ACTION_TAG);
             out.writeInt(this.viewId);
             out.writeString(this.methodName);
             out.writeInt(this.type);
@@ -1375,7 +1298,7 @@
             // we have written a valid value to the parcel.
             switch (this.type) {
                 case BOOLEAN:
-                    out.writeInt((Boolean) this.value ? 1 : 0);
+                    out.writeBoolean((Boolean) this.value);
                     break;
                 case BYTE:
                     out.writeByte((Byte) this.value);
@@ -1412,10 +1335,7 @@
                 case INTENT:
                 case COLOR_STATE_LIST:
                 case ICON:
-                    out.writeInt(this.value != null ? 1 : 0);
-                    if (this.value != null) {
-                        ((Parcelable) this.value).writeToParcel(out, flags);
-                    }
+                    out.writeTypedObject((Parcelable) this.value, flags);
                     break;
                 default:
                     break;
@@ -1521,10 +1441,16 @@
             }
         }
 
-        public String getActionName() {
+        @Override
+        public int getActionTag() {
+            return REFLECTION_ACTION_TAG;
+        }
+
+        @Override
+        public String getUniqueKey() {
             // Each type of reflection action corresponds to a setter, so each should be seen as
             // unique from the standpoint of merging.
-            return "ReflectionAction" + this.methodName + this.type;
+            return super.getUniqueKey() + this.methodName + this.type;
         }
 
         @Override
@@ -1586,7 +1512,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(VIEW_GROUP_ACTION_ADD_TAG);
             dest.writeInt(viewId);
             dest.writeInt(mIndex);
             mNestedViews.writeToParcel(dest, flags);
@@ -1661,10 +1586,9 @@
             return mNestedViews.prefersAsyncApply();
         }
 
-
         @Override
-        public String getActionName() {
-            return "ViewGroupActionAdd";
+        public int getActionTag() {
+            return VIEW_GROUP_ACTION_ADD_TAG;
         }
     }
 
@@ -1696,7 +1620,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(VIEW_GROUP_ACTION_REMOVE_TAG);
             dest.writeInt(viewId);
             dest.writeInt(mViewIdToKeep);
         }
@@ -1762,8 +1685,8 @@
         }
 
         @Override
-        public String getActionName() {
-            return "ViewGroupActionRemove";
+        public int getActionTag() {
+            return VIEW_GROUP_ACTION_REMOVE_TAG;
         }
 
         @Override
@@ -1803,18 +1726,10 @@
             isRelative = (parcel.readInt() != 0);
             useIcons = (parcel.readInt() != 0);
             if (useIcons) {
-                if (parcel.readInt() != 0) {
-                    i1 = Icon.CREATOR.createFromParcel(parcel);
-                }
-                if (parcel.readInt() != 0) {
-                    i2 = Icon.CREATOR.createFromParcel(parcel);
-                }
-                if (parcel.readInt() != 0) {
-                    i3 = Icon.CREATOR.createFromParcel(parcel);
-                }
-                if (parcel.readInt() != 0) {
-                    i4 = Icon.CREATOR.createFromParcel(parcel);
-                }
+                i1 = parcel.readTypedObject(Icon.CREATOR);
+                i2 = parcel.readTypedObject(Icon.CREATOR);
+                i3 = parcel.readTypedObject(Icon.CREATOR);
+                i4 = parcel.readTypedObject(Icon.CREATOR);
             } else {
                 d1 = parcel.readInt();
                 d2 = parcel.readInt();
@@ -1824,35 +1739,14 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(TEXT_VIEW_DRAWABLE_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeInt(isRelative ? 1 : 0);
             dest.writeInt(useIcons ? 1 : 0);
             if (useIcons) {
-                if (i1 != null) {
-                    dest.writeInt(1);
-                    i1.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
-                if (i2 != null) {
-                    dest.writeInt(1);
-                    i2.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
-                if (i3 != null) {
-                    dest.writeInt(1);
-                    i3.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
-                if (i4 != null) {
-                    dest.writeInt(1);
-                    i4.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
+                dest.writeTypedObject(i1, 0);
+                dest.writeTypedObject(i2, 0);
+                dest.writeTypedObject(i3, 0);
+                dest.writeTypedObject(i4, 0);
             } else {
                 dest.writeInt(d1);
                 dest.writeInt(d2);
@@ -1923,8 +1817,9 @@
             return useIcons;
         }
 
-        public String getActionName() {
-            return "TextViewDrawableAction";
+        @Override
+        public int getActionTag() {
+            return TEXT_VIEW_DRAWABLE_ACTION_TAG;
         }
 
         boolean isRelative = false;
@@ -1953,7 +1848,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(TEXT_VIEW_SIZE_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeInt(units);
             dest.writeFloat(size);
@@ -1966,8 +1860,9 @@
             target.setTextSize(units, size);
         }
 
-        public String getActionName() {
-            return "TextViewSizeAction";
+        @Override
+        public int getActionTag() {
+            return TEXT_VIEW_SIZE_ACTION_TAG;
         }
 
         int units;
@@ -1995,7 +1890,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(VIEW_PADDING_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeInt(left);
             dest.writeInt(top);
@@ -2010,8 +1904,9 @@
             target.setPadding(left, top, right, bottom);
         }
 
-        public String getActionName() {
-            return "ViewPaddingAction";
+        @Override
+        public int getActionTag() {
+            return VIEW_PADDING_ACTION_TAG;
         }
 
         int left, top, right, bottom;
@@ -2028,6 +1923,9 @@
         public static final int LAYOUT_WIDTH = 2;
         public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3;
 
+        final int mProperty;
+        final int mValue;
+
         /**
          * @param viewId ID of the view alter
          * @param property which layout parameter to alter
@@ -2035,21 +1933,20 @@
          */
         public LayoutParamAction(int viewId, int property, int value) {
             this.viewId = viewId;
-            this.property = property;
-            this.value = value;
+            this.mProperty = property;
+            this.mValue = value;
         }
 
         public LayoutParamAction(Parcel parcel) {
             viewId = parcel.readInt();
-            property = parcel.readInt();
-            value = parcel.readInt();
+            mProperty = parcel.readInt();
+            mValue = parcel.readInt();
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(LAYOUT_PARAM_ACTION_TAG);
             dest.writeInt(viewId);
-            dest.writeInt(property);
-            dest.writeInt(value);
+            dest.writeInt(mProperty);
+            dest.writeInt(mValue);
         }
 
         @Override
@@ -2062,27 +1959,27 @@
             if (layoutParams == null) {
                 return;
             }
-            switch (property) {
+            switch (mProperty) {
                 case LAYOUT_MARGIN_END_DIMEN:
                     if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
-                        int resolved = resolveDimenPixelOffset(target, value);
+                        int resolved = resolveDimenPixelOffset(target, mValue);
                         ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(resolved);
                         target.setLayoutParams(layoutParams);
                     }
                     break;
                 case LAYOUT_MARGIN_BOTTOM_DIMEN:
                     if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
-                        int resolved = resolveDimenPixelOffset(target, value);
+                        int resolved = resolveDimenPixelOffset(target, mValue);
                         ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved;
                         target.setLayoutParams(layoutParams);
                     }
                     break;
                 case LAYOUT_WIDTH:
-                    layoutParams.width = value;
+                    layoutParams.width = mValue;
                     target.setLayoutParams(layoutParams);
                     break;
                 default:
-                    throw new IllegalArgumentException("Unknown property " + property);
+                    throw new IllegalArgumentException("Unknown property " + mProperty);
             }
         }
 
@@ -2093,79 +1990,15 @@
             return target.getContext().getResources().getDimensionPixelOffset(value);
         }
 
-        public String getActionName() {
-            return "LayoutParamAction" + property + ".";
-        }
-
-        int property;
-        int value;
-    }
-
-    /**
-     * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
-     * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
-     */
-    private class TextViewDrawableColorFilterAction extends Action {
-        public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index,
-                int color, PorterDuff.Mode mode) {
-            this.viewId = viewId;
-            this.isRelative = isRelative;
-            this.index = index;
-            this.color = color;
-            this.mode = mode;
-        }
-
-        public TextViewDrawableColorFilterAction(Parcel parcel) {
-            viewId = parcel.readInt();
-            isRelative = (parcel.readInt() != 0);
-            index = parcel.readInt();
-            color = parcel.readInt();
-            mode = readPorterDuffMode(parcel);
-        }
-
-        private PorterDuff.Mode readPorterDuffMode(Parcel parcel) {
-            int mode = parcel.readInt();
-            if (mode >= 0 && mode < PorterDuff.Mode.values().length) {
-                return PorterDuff.Mode.values()[mode];
-            } else {
-                return PorterDuff.Mode.CLEAR;
-            }
-        }
-
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG);
-            dest.writeInt(viewId);
-            dest.writeInt(isRelative ? 1 : 0);
-            dest.writeInt(index);
-            dest.writeInt(color);
-            dest.writeInt(mode.ordinal());
+        @Override
+        public int getActionTag() {
+            return LAYOUT_PARAM_ACTION_TAG;
         }
 
         @Override
-        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
-            final TextView target = root.findViewById(viewId);
-            if (target == null) return;
-            Drawable[] drawables = isRelative
-                    ? target.getCompoundDrawablesRelative()
-                    : target.getCompoundDrawables();
-            if (index < 0 || index >= 4) {
-                throw new IllegalStateException("index must be in range [0, 3].");
-            }
-            Drawable d = drawables[index];
-            if (d != null) {
-                d.mutate();
-                d.setColorFilter(color, mode);
-            }
+        public String getUniqueKey() {
+            return super.getUniqueKey() + mProperty;
         }
-
-        public String getActionName() {
-            return "TextViewDrawableColorFilterAction";
-        }
-
-        final boolean isRelative;
-        final int index;
-        final int color;
-        final PorterDuff.Mode mode;
     }
 
     /**
@@ -2184,7 +2017,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_REMOTE_INPUTS_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeTypedArray(remoteInputs, flags);
         }
@@ -2197,8 +2029,9 @@
             target.setTagInternal(R.id.remote_input_tag, remoteInputs);
         }
 
-        public String getActionName() {
-            return "SetRemoteInputsAction";
+        @Override
+        public int getActionTag() {
+            return SET_REMOTE_INPUTS_ACTION_TAG;
         }
 
         final Parcelable[] remoteInputs;
@@ -2220,7 +2053,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(OVERRIDE_TEXT_COLORS_TAG);
             dest.writeInt(textColor);
         }
 
@@ -2245,8 +2077,9 @@
             }
         }
 
-        public String getActionName() {
-            return "OverrideTextColorsAction";
+        @Override
+        public int getActionTag() {
+            return OVERRIDE_TEXT_COLORS_TAG;
         }
     }
 
@@ -2338,20 +2171,12 @@
         }
 
         if (src.mActions != null) {
-            mActions = new ArrayList<>();
-
             Parcel p = Parcel.obtain();
-            int count = src.mActions.size();
-            for (int i = 0; i < count; i++) {
-                p.setDataPosition(0);
-                Action a = src.mActions.get(i);
-                a.writeToParcel(
-                        p, a.hasSameAppInfo(mApplication) ? PARCELABLE_ELIDE_DUPLICATES : 0);
-                p.setDataPosition(0);
-                // Since src is already in memory, we do not care about stack overflow as it has
-                // already been read once.
-                mActions.add(getActionFromParcel(p, 0));
-            }
+            src.writeActionsToParcel(p);
+            p.setDataPosition(0);
+            // Since src is already in memory, we do not care about stack overflow as it has
+            // already been read once.
+            readActionsFromParcel(p, 0);
             p.recycle();
         }
 
@@ -2392,13 +2217,7 @@
             mLayoutId = parcel.readInt();
             mIsWidgetCollectionChild = parcel.readInt() == 1;
 
-            int count = parcel.readInt();
-            if (count > 0) {
-                mActions = new ArrayList<>(count);
-                for (int i = 0; i < count; i++) {
-                    mActions.add(getActionFromParcel(parcel, depth));
-                }
-            }
+            readActionsFromParcel(parcel, depth);
         } else {
             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
             mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
@@ -2409,21 +2228,31 @@
         mReapplyDisallowed = parcel.readInt() == 0;
     }
 
+    private void readActionsFromParcel(Parcel parcel, int depth) {
+        int count = parcel.readInt();
+        if (count > 0) {
+            mActions = new ArrayList<>(count);
+            for (int i = 0; i < count; i++) {
+                mActions.add(getActionFromParcel(parcel, depth));
+            }
+        }
+    }
+
     private Action getActionFromParcel(Parcel parcel, int depth) {
         int tag = parcel.readInt();
         switch (tag) {
             case SET_ON_CLICK_PENDING_INTENT_TAG:
                 return new SetOnClickPendingIntent(parcel);
-            case SET_DRAWABLE_PARAMETERS_TAG:
-                return new SetDrawableParameters(parcel);
+            case SET_DRAWABLE_TINT_TAG:
+                return new SetDrawableTint(parcel);
             case REFLECTION_ACTION_TAG:
                 return new ReflectionAction(parcel);
             case VIEW_GROUP_ACTION_ADD_TAG:
                 return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth);
             case VIEW_GROUP_ACTION_REMOVE_TAG:
                 return new ViewGroupActionRemove(parcel);
-            case SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG:
-                return new ReflectionActionWithoutParams(parcel);
+            case VIEW_CONTENT_NAVIGATION_TAG:
+                return new ViewContentNavigation(parcel);
             case SET_EMPTY_VIEW_ACTION_TAG:
                 return new SetEmptyView(parcel);
             case SET_PENDING_INTENT_TEMPLATE_TAG:
@@ -2442,8 +2271,6 @@
                 return new BitmapReflectionAction(parcel);
             case SET_REMOTE_VIEW_ADAPTER_LIST_TAG:
                 return new SetRemoteViewsAdapterList(parcel);
-            case TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG:
-                return new TextViewDrawableColorFilterAction(parcel);
             case SET_REMOTE_INPUTS_ACTION_TAG:
                 return new SetRemoteInputsAction(parcel);
             case LAYOUT_PARAM_ACTION_TAG:
@@ -2600,7 +2427,7 @@
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
      */
     public void showNext(int viewId) {
-        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
+        addAction(new ViewContentNavigation(viewId, true /* next */));
     }
 
     /**
@@ -2609,7 +2436,7 @@
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
      */
     public void showPrevious(int viewId) {
-        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
+        addAction(new ViewContentNavigation(viewId, false /* next */));
     }
 
     /**
@@ -2683,28 +2510,6 @@
     }
 
     /**
-     * Equivalent to applying a color filter on one of the drawables in
-     * {@link android.widget.TextView#getCompoundDrawablesRelative()}.
-     *
-     * @param viewId The id of the view whose text should change.
-     * @param index  The index of the drawable in the array of
-     *               {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color
-     *               filter on. Must be in [0, 3].
-     * @param color  The color of the color filter. See
-     *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
-     * @param mode   The mode of the color filter. See
-     *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
-     * @hide
-     */
-    public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId,
-            int index, int color, PorterDuff.Mode mode) {
-        if (index < 0 || index >= 4) {
-            throw new IllegalArgumentException("index must be in range [0, 3].");
-        }
-        addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode));
-    }
-
-    /**
      * Equivalent to calling {@link
      * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
      * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
@@ -2901,12 +2706,10 @@
 
     /**
      * @hide
-     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+     * Equivalent to calling
      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
-     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
-     * view.
+     * on the {@link Drawable} of a given view.
      * <p>
-     * You can omit specific calls by marking their values with null or -1.
      *
      * @param viewId The id of the view that contains the target
      *            {@link Drawable}
@@ -2915,20 +2718,15 @@
      *            {@link android.view.View#getBackground()}. Otherwise, assume
      *            the target view is an {@link ImageView} and apply them to
      *            {@link ImageView#getDrawable()}.
-     * @param alpha Specify an alpha value for the drawable, or -1 to leave
-     *            unchanged.
      * @param colorFilter Specify a color for a
      *            {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
      *            {@code mode} is {@code null}.
      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
      *            unchanged.
-     * @param level Specify the level for the drawable, or -1 to leave
-     *            unchanged.
      */
-    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
-            int colorFilter, PorterDuff.Mode mode, int level) {
-        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
-                colorFilter, mode, level));
+    public void setDrawableTint(int viewId, boolean targetBackground,
+            int colorFilter, @NonNull PorterDuff.Mode mode) {
+        addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode));
     }
 
     /**
@@ -3695,18 +3493,7 @@
             }
             dest.writeInt(mLayoutId);
             dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
-            int count;
-            if (mActions != null) {
-                count = mActions.size();
-            } else {
-                count = 0;
-            }
-            dest.writeInt(count);
-            for (int i=0; i<count; i++) {
-                Action a = mActions.get(i);
-                a.writeToParcel(dest, a.hasSameAppInfo(mApplication)
-                        ? PARCELABLE_ELIDE_DUPLICATES : 0);
-            }
+            writeActionsToParcel(dest);
         } else {
             dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
             // We only write the bitmap cache if we are the root RemoteViews, as this cache
@@ -3721,6 +3508,22 @@
         dest.writeInt(mReapplyDisallowed ? 1 : 0);
     }
 
+    private void writeActionsToParcel(Parcel parcel) {
+        int count;
+        if (mActions != null) {
+            count = mActions.size();
+        } else {
+            count = 0;
+        }
+        parcel.writeInt(count);
+        for (int i = 0; i < count; i++) {
+            Action a = mActions.get(i);
+            parcel.writeInt(a.getActionTag());
+            a.writeToParcel(parcel, a.hasSameAppInfo(mApplication)
+                    ? PARCELABLE_ELIDE_DUPLICATES : 0);
+        }
+    }
+
     private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
         if (packageName == null) {
             return null;
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 36dc330..3be42a5 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -43,9 +43,11 @@
 
 import java.text.BreakIterator;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
@@ -58,11 +60,7 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public final class SelectionActionModeHelper {
 
-    /**
-     * Maximum time (in milliseconds) to wait for a result before timing out.
-     */
-    // TODO: Consider making this a ViewConfiguration.
-    private static final int TIMEOUT_DURATION = 200;
+    private static final String LOG_TAG = "SelectActionModeHelper";
 
     private static final boolean SMART_SELECT_ANIMATION_ENABLED = true;
 
@@ -83,7 +81,8 @@
         mEditor = Preconditions.checkNotNull(editor);
         mTextView = mEditor.getTextView();
         mTextClassificationHelper = new TextClassificationHelper(
-                mTextView.getTextClassifier(), mTextView.getText(),
+                mTextView.getTextClassifier(),
+                getText(mTextView),
                 0, 1, mTextView.getTextLocales());
         mSelectionTracker = new SelectionTracker(mTextView);
 
@@ -97,7 +96,7 @@
 
     public void startActionModeAsync(boolean adjustSelection) {
         mSelectionTracker.onOriginalSelection(
-                mTextView.getText(),
+                getText(mTextView),
                 mTextView.getSelectionStart(),
                 mTextView.getSelectionEnd(),
                 mTextView.isTextEditable());
@@ -108,7 +107,7 @@
             resetTextClassificationHelper();
             mTextClassificationAsyncTask = new TextClassificationAsyncTask(
                     mTextView,
-                    TIMEOUT_DURATION,
+                    mTextClassificationHelper.getTimeoutDuration(),
                     adjustSelection
                             ? mTextClassificationHelper::suggestSelection
                             : mTextClassificationHelper::classifyText,
@@ -127,7 +126,7 @@
             resetTextClassificationHelper();
             mTextClassificationAsyncTask = new TextClassificationAsyncTask(
                     mTextView,
-                    TIMEOUT_DURATION,
+                    mTextClassificationHelper.getTimeoutDuration(),
                     mTextClassificationHelper::classifyText,
                     this::invalidateActionMode)
                     .execute();
@@ -195,7 +194,7 @@
     }
 
     private void startActionMode(@Nullable SelectionResult result) {
-        final CharSequence text = mTextView.getText();
+        final CharSequence text = getText(mTextView);
         if (result != null && text instanceof Spannable) {
             Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
             mTextClassification = result.mClassification;
@@ -229,7 +228,7 @@
             return;
         }
 
-        final List<RectF> selectionRectangles =
+        final List<SmartSelectSprite.RectangleWithTextSelectionLayout> selectionRectangles =
                 convertSelectionToRectangles(layout, result.mStart, result.mEnd);
 
         final PointF touchPoint = new PointF(
@@ -237,7 +236,8 @@
                 mEditor.getLastUpPositionY());
 
         final PointF animationStartPoint =
-                movePointInsideNearestRectangle(touchPoint, selectionRectangles);
+                movePointInsideNearestRectangle(touchPoint, selectionRectangles,
+                        SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle);
 
         mSmartSelectSprite.startAnimation(
                 animationStartPoint,
@@ -245,38 +245,58 @@
                 onAnimationEndCallback);
     }
 
-    private List<RectF> convertSelectionToRectangles(final Layout layout, final int start,
-            final int end) {
-        final List<RectF> result = new ArrayList<>();
-        layout.getSelection(start, end, (left, top, right, bottom, textSelectionLayout) ->
-                mergeRectangleIntoList(result, new RectF(left, top, right, bottom)));
+    private List<SmartSelectSprite.RectangleWithTextSelectionLayout> convertSelectionToRectangles(
+            final Layout layout, final int start, final int end) {
+        final List<SmartSelectSprite.RectangleWithTextSelectionLayout> result = new ArrayList<>();
 
-        result.sort(SmartSelectSprite.RECTANGLE_COMPARATOR);
+        final Layout.SelectionRectangleConsumer consumer =
+                (left, top, right, bottom, textSelectionLayout) -> mergeRectangleIntoList(
+                        result,
+                        new RectF(left, top, right, bottom),
+                        SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+                        r -> new SmartSelectSprite.RectangleWithTextSelectionLayout(r,
+                                textSelectionLayout)
+                );
+
+        layout.getSelection(start, end, consumer);
+
+        result.sort(Comparator.comparing(
+                SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+                SmartSelectSprite.RECTANGLE_COMPARATOR));
+
         return result;
     }
 
+    // TODO: Move public pure functions out of this class and make it package-private.
     /**
-     * Merges a {@link RectF} into an existing list of rectangles. While merging, this method
-     * makes sure that:
+     * Merges a {@link RectF} into an existing list of any objects which contain a rectangle.
+     * While merging, this method makes sure that:
      *
      * <ol>
      * <li>No rectangle is redundant (contained within a bigger rectangle)</li>
      * <li>Rectangles of the same height and vertical position that intersect get merged</li>
      * </ol>
      *
-     * @param list      the list of rectangles to merge the new rectangle in
+     * @param list      the list of rectangles (or other rectangle containers) to merge the new
+     *                  rectangle into
      * @param candidate the {@link RectF} to merge into the list
+     * @param extractor a function that can extract a {@link RectF} from an element of the given
+     *                  list
+     * @param packer    a function that can wrap the resulting {@link RectF} into an element that
+     *                  the list contains
      * @hide
      */
     @VisibleForTesting
-    public static void mergeRectangleIntoList(List<RectF> list, RectF candidate) {
+    public static <T> void mergeRectangleIntoList(final List<T> list,
+            final RectF candidate, final Function<T, RectF> extractor,
+            final Function<RectF, T> packer) {
         if (candidate.isEmpty()) {
             return;
         }
 
         final int elementCount = list.size();
         for (int index = 0; index < elementCount; ++index) {
-            final RectF existingRectangle = list.get(index);
+            final RectF existingRectangle = extractor.apply(list.get(index));
             if (existingRectangle.contains(candidate)) {
                 return;
             }
@@ -299,26 +319,27 @@
         }
 
         for (int index = elementCount - 1; index >= 0; --index) {
-            if (list.get(index).isEmpty()) {
+            final RectF rectangle = extractor.apply(list.get(index));
+            if (rectangle.isEmpty()) {
                 list.remove(index);
             }
         }
 
-        list.add(candidate);
+        list.add(packer.apply(candidate));
     }
 
 
     /** @hide */
     @VisibleForTesting
-    public static PointF movePointInsideNearestRectangle(final PointF point,
-            final List<RectF> rectangles) {
+    public static <T> PointF movePointInsideNearestRectangle(final PointF point,
+            final List<T> list, final Function<T, RectF> extractor) {
         float bestX = -1;
         float bestY = -1;
         double bestDistance = Double.MAX_VALUE;
 
-        final int elementCount = rectangles.size();
+        final int elementCount = list.size();
         for (int index = 0; index < elementCount; ++index) {
-            final RectF rectangle = rectangles.get(index);
+            final RectF rectangle = extractor.apply(list.get(index));
             final float candidateY = rectangle.centerY();
             final float candidateX;
 
@@ -356,7 +377,9 @@
     }
 
     private void resetTextClassificationHelper() {
-        mTextClassificationHelper.reset(mTextView.getTextClassifier(), mTextView.getText(),
+        mTextClassificationHelper.reset(
+                mTextView.getTextClassifier(),
+                getText(mTextView),
                 mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
                 mTextView.getTextLocales());
     }
@@ -382,6 +405,7 @@
         private int mSelectionStart;
         private int mSelectionEnd;
         private boolean mAllowReset;
+        private final LogAbandonRunnable mDelayedLogAbandon = new LogAbandonRunnable();
 
         SelectionTracker(TextView textView) {
             mTextView = Preconditions.checkNotNull(textView);
@@ -393,6 +417,10 @@
          */
         public void onOriginalSelection(
                 CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+            // 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();
+
             mOriginalStart = mSelectionStart = selectionStart;
             mOriginalEnd = mSelectionEnd = selectionEnd;
             mAllowReset = false;
@@ -433,12 +461,7 @@
         public void onSelectionDestroyed() {
             mAllowReset = false;
             // Wait a few ms to see if the selection was destroyed because of a text change event.
-            mTextView.postDelayed(() -> {
-                mLogger.logSelectionAction(
-                        mSelectionStart, mSelectionEnd,
-                        SelectionEvent.ActionType.ABANDON, null /* classification */);
-                mSelectionStart = mSelectionEnd = -1;
-            }, 100 /* ms */);
+            mDelayedLogAbandon.schedule(100 /* ms */);
         }
 
         /**
@@ -465,7 +488,7 @@
             if (isSelectionStarted()
                     && mAllowReset
                     && textIndex >= mSelectionStart && textIndex <= mSelectionEnd
-                    && textView.getText() instanceof Spannable) {
+                    && getText(textView) instanceof Spannable) {
                 mAllowReset = false;
                 boolean selected = editor.selectCurrentWord();
                 if (selected) {
@@ -495,6 +518,38 @@
         private boolean isSelectionStarted() {
             return mSelectionStart >= 0 && mSelectionEnd >= 0 && mSelectionStart != mSelectionEnd;
         }
+
+        /** A helper for keeping track of pending abandon logging requests. */
+        private final class LogAbandonRunnable implements Runnable {
+            private boolean mIsPending;
+
+            /** Schedules an abandon to be logged with the given delay. Flush if necessary. */
+            void schedule(int delayMillis) {
+                if (mIsPending) {
+                    Log.e(LOG_TAG, "Force flushing abandon due to new scheduling request");
+                    flush();
+                }
+                mIsPending = true;
+                mTextView.postDelayed(this, delayMillis);
+            }
+
+            /** If there is a pending log request, execute it now. */
+            void flush() {
+                mTextView.removeCallbacks(this);
+                run();
+            }
+
+            @Override
+            public void run() {
+                if (mIsPending) {
+                    mLogger.logSelectionAction(
+                            mSelectionStart, mSelectionEnd,
+                            SelectionEvent.ActionType.ABANDON, null /* classification */);
+                    mSelectionStart = mSelectionEnd = -1;
+                    mIsPending = false;
+                }
+            }
+        }
     }
 
     // TODO: Write tests
@@ -689,7 +744,7 @@
             mSelectionResultSupplier = Preconditions.checkNotNull(selectionResultSupplier);
             mSelectionResultCallback = Preconditions.checkNotNull(selectionResultCallback);
             // Make a copy of the original text.
-            mOriginalText = mTextView.getText().toString();
+            mOriginalText = getText(mTextView).toString();
         }
 
         @Override
@@ -705,7 +760,7 @@
         @Override
         @UiThread
         protected void onPostExecute(SelectionResult result) {
-            result = TextUtils.equals(mOriginalText, mTextView.getText()) ? result : null;
+            result = TextUtils.equals(mOriginalText, getText(mTextView)) ? result : null;
             mSelectionResultCallback.accept(result);
         }
 
@@ -752,6 +807,9 @@
         private LocaleList mLastClassificationLocales;
         private SelectionResult mLastClassificationResult;
 
+        /** Whether the TextClassifier has been initialized. */
+        private boolean mHot;
+
         TextClassificationHelper(TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
             reset(textClassifier, text, selectionStart, selectionEnd, locales);
@@ -771,11 +829,13 @@
 
         @WorkerThread
         public SelectionResult classifyText() {
+            mHot = true;
             return performClassification(null /* selection */);
         }
 
         @WorkerThread
         public SelectionResult suggestSelection() {
+            mHot = true;
             trimText();
             final TextSelection selection = mTextClassifier.suggestSelection(
                     mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
@@ -784,6 +844,22 @@
             return performClassification(selection);
         }
 
+        /**
+         * Maximum time (in milliseconds) to wait for a textclassifier result before timing out.
+         */
+        // TODO: Consider making this a ViewConfiguration.
+        public int getTimeoutDuration() {
+            if (mHot) {
+                return 200;
+            } else {
+                // Return a slightly larger number than usual when the TextClassifier is first
+                // initialized. Initialization would usually take longer than subsequent calls to
+                // the TextClassifier. The impact of this on the UI is that we do not show the
+                // selection handles or toolbar until after this timeout.
+                return 500;
+            }
+        }
+
         private SelectionResult performClassification(@Nullable TextSelection selection) {
             if (!Objects.equals(mText, mLastClassificationText)
                     || mSelectionStart != mLastClassificationSelectionStart
@@ -854,4 +930,14 @@
                 return SelectionEvent.ActionType.OTHER;
         }
     }
+
+    private static CharSequence getText(TextView textView) {
+        // Extracts the textView's text.
+        // TODO: Investigate why/when TextView.getText() is null.
+        final CharSequence text = textView.getText();
+        if (text != null) {
+            return text;
+        }
+        return "";
+    }
 }
diff --git a/android/widget/SmartSelectSprite.java b/android/widget/SmartSelectSprite.java
index 27b93bc..a391c6e 100644
--- a/android/widget/SmartSelectSprite.java
+++ b/android/widget/SmartSelectSprite.java
@@ -35,6 +35,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.Shape;
+import android.text.Layout;
 import android.util.TypedValue;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -42,9 +43,9 @@
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.LinkedList;
 import java.util.List;
 
 /**
@@ -76,6 +77,26 @@
     private Drawable mExistingDrawable = null;
     private RectangleList mExistingRectangleList = null;
 
+    static final class RectangleWithTextSelectionLayout {
+        private final RectF mRectangle;
+        @Layout.TextSelectionLayout
+        private final int mTextSelectionLayout;
+
+        RectangleWithTextSelectionLayout(RectF rectangle, int textSelectionLayout) {
+            mRectangle = Preconditions.checkNotNull(rectangle);
+            mTextSelectionLayout = textSelectionLayout;
+        }
+
+        public RectF getRectangle() {
+            return mRectangle;
+        }
+
+        @Layout.TextSelectionLayout
+        public int getTextSelectionLayout() {
+            return mTextSelectionLayout;
+        }
+    }
+
     /**
      * A rounded rectangle with a configurable corner radius and the ability to expand outside of
      * its bounding rectangle and clip against it.
@@ -84,12 +105,23 @@
 
         private static final String PROPERTY_ROUND_RATIO = "roundRatio";
 
+        /**
+         * The direction in which the rectangle will perform its expansion. A rectangle can expand
+         * from its left edge, its right edge or from the center (or, more precisely, the user's
+         * touch point). For example, in left-to-right text, a selection spanning two lines with the
+         * user's action being on the first line will have the top rectangle and expansion direction
+         * of CENTER, while the bottom one will have an expansion direction of RIGHT.
+         */
         @Retention(SOURCE)
         @IntDef({ExpansionDirection.LEFT, ExpansionDirection.CENTER, ExpansionDirection.RIGHT})
         private @interface ExpansionDirection {
-        int LEFT = 0;
-        int CENTER = 1;
-        int RIGHT = 2;
+            int LEFT = -1;
+            int CENTER = 0;
+            int RIGHT = 1;
+        }
+
+        private static @ExpansionDirection int invert(@ExpansionDirection int expansionDirection) {
+            return expansionDirection * -1;
         }
 
         @Retention(SOURCE)
@@ -114,20 +146,33 @@
         private final RectF mClipRect = new RectF();
         private final Path mClipPath = new Path();
 
-        /** How far offset the left edge of the rectangle is from the bounding box. */
+        /** How offset the left edge of the rectangle is from the left side of the bounding box. */
         private float mLeftBoundary = 0;
-        /** How far offset the right edge of the rectangle is from the bounding box. */
+        /** How offset the right edge of the rectangle is from the left side of the bounding box. */
         private float mRightBoundary = 0;
 
+        /** Whether the horizontal bounds are inverted (for RTL scenarios). */
+        private final boolean mInverted;
+
+        private final float mBoundingWidth;
+
         private RoundedRectangleShape(
                 final RectF boundingRectangle,
                 final @ExpansionDirection int expansionDirection,
                 final @RectangleBorderType int rectangleBorderType,
+                final boolean inverted,
                 final float strokeWidth) {
             mBoundingRectangle = new RectF(boundingRectangle);
-            mExpansionDirection = expansionDirection;
+            mBoundingWidth = boundingRectangle.width();
             mRectangleBorderType = rectangleBorderType;
             mStrokeWidth = strokeWidth;
+            mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
+
+            if (inverted) {
+                mExpansionDirection = invert(expansionDirection);
+            } else {
+                mExpansionDirection = expansionDirection;
+            }
 
             if (boundingRectangle.height() > boundingRectangle.width()) {
                 setRoundRatio(0.0f);
@@ -148,6 +193,10 @@
          */
         @Override
         public void draw(Canvas canvas, Paint paint) {
+            if (mLeftBoundary == mRightBoundary) {
+                return;
+            }
+
             final float cornerRadius = getCornerRadius();
             final float adjustedCornerRadius = getAdjustedCornerRadius();
 
@@ -157,7 +206,7 @@
 
             if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
                 mDrawRect.left -= cornerRadius / 2;
-                mDrawRect.right -= cornerRadius / 2;
+                mDrawRect.right += cornerRadius / 2;
             } else {
                 switch (mExpansionDirection) {
                     case ExpansionDirection.CENTER:
@@ -173,7 +222,7 @@
 
             canvas.save();
             mClipRect.set(mBoundingRectangle);
-            mClipRect.inset(-mStrokeWidth, -mStrokeWidth);
+            mClipRect.inset(-mStrokeWidth / 2, -mStrokeWidth / 2);
             canvas.clipRect(mClipRect);
             canvas.drawRoundRect(mDrawRect, adjustedCornerRadius, adjustedCornerRadius, paint);
             canvas.restore();
@@ -190,20 +239,28 @@
             canvas.restore();
         }
 
-        public void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
+        void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
             mRoundRatio = roundRatio;
         }
 
-        public float getRoundRatio() {
+        float getRoundRatio() {
             return mRoundRatio;
         }
 
-        private void setLeftBoundary(final float leftBoundary) {
-            mLeftBoundary = leftBoundary;
+        private void setStartBoundary(final float startBoundary) {
+            if (mInverted) {
+                mRightBoundary = mBoundingWidth - startBoundary;
+            } else {
+                mLeftBoundary = startBoundary;
+            }
         }
 
-        private void setRightBoundary(final float rightBoundary) {
-            mRightBoundary = rightBoundary;
+        private void setEndBoundary(final float endBoundary) {
+            if (mInverted) {
+                mLeftBoundary = mBoundingWidth - endBoundary;
+            } else {
+                mRightBoundary = endBoundary;
+            }
         }
 
         private float getCornerRadius() {
@@ -247,8 +304,8 @@
         private @DisplayType int mDisplayType = DisplayType.RECTANGLES;
 
         private RectangleList(final List<RoundedRectangleShape> rectangles) {
-            mRectangles = new LinkedList<>(rectangles);
-            mReversedRectangles = new LinkedList<>(rectangles);
+            mRectangles = new ArrayList<>(rectangles);
+            mReversedRectangles = new ArrayList<>(rectangles);
             Collections.reverse(mReversedRectangles);
             mOutlinePolygonPath = generateOutlinePolygonPath(rectangles);
         }
@@ -258,11 +315,11 @@
             for (RoundedRectangleShape rectangle : mReversedRectangles) {
                 final float rectangleLeftBoundary = boundarySoFar - rectangle.getBoundingWidth();
                 if (leftBoundary < rectangleLeftBoundary) {
-                    rectangle.setLeftBoundary(0);
+                    rectangle.setStartBoundary(0);
                 } else if (leftBoundary > boundarySoFar) {
-                    rectangle.setLeftBoundary(rectangle.getBoundingWidth());
+                    rectangle.setStartBoundary(rectangle.getBoundingWidth());
                 } else {
-                    rectangle.setLeftBoundary(
+                    rectangle.setStartBoundary(
                             rectangle.getBoundingWidth() - boundarySoFar + leftBoundary);
                 }
 
@@ -275,11 +332,11 @@
             for (RoundedRectangleShape rectangle : mRectangles) {
                 final float rectangleRightBoundary = rectangle.getBoundingWidth() + boundarySoFar;
                 if (rectangleRightBoundary < rightBoundary) {
-                    rectangle.setRightBoundary(rectangle.getBoundingWidth());
+                    rectangle.setEndBoundary(rectangle.getBoundingWidth());
                 } else if (boundarySoFar > rightBoundary) {
-                    rectangle.setRightBoundary(0);
+                    rectangle.setEndBoundary(0);
                 } else {
-                    rectangle.setRightBoundary(rightBoundary - boundarySoFar);
+                    rectangle.setEndBoundary(rightBoundary - boundarySoFar);
                 }
 
                 boundarySoFar = rectangleRightBoundary;
@@ -331,8 +388,8 @@
     }
 
     /**
-     * @param context     The {@link Context} in which the animation will run
-     * @param invalidator A {@link Runnable} which will be called every time the animation updates,
+     * @param context     the {@link Context} in which the animation will run
+     * @param invalidator a {@link Runnable} which will be called every time the animation updates,
      *                    indicating that the view drawing the animation should invalidate itself
      */
     SmartSelectSprite(final Context context, final Runnable invalidator) {
@@ -356,67 +413,97 @@
      *                              "selection" and finally join them into a single polygon. In
      *                              order to get the correct visual behavior, these rectangles
      *                              should be sorted according to {@link #RECTANGLE_COMPARATOR}.
-     * @param onAnimationEnd        The callback which will be invoked once the whole animation
-     *                              completes.
+     * @param onAnimationEnd        the callback which will be invoked once the whole animation
+     *                              completes
      * @throws IllegalArgumentException if the given start point is not in any of the
-     *                                  destinationRectangles.
+     *                                  destinationRectangles
      * @see #cancelAnimation()
      */
+    // TODO nullability checks on parameters
     public void startAnimation(
             final PointF start,
-            final List<RectF> destinationRectangles,
-            final Runnable onAnimationEnd) throws IllegalArgumentException {
+            final List<RectangleWithTextSelectionLayout> destinationRectangles,
+            final Runnable onAnimationEnd) {
         cancelAnimation();
 
         final ValueAnimator.AnimatorUpdateListener updateListener =
                 valueAnimator -> mInvalidator.run();
 
-        final List<RoundedRectangleShape> shapes = new LinkedList<>();
-        final List<Animator> cornerAnimators = new LinkedList<>();
+        final int rectangleCount = destinationRectangles.size();
 
-        final RectF centerRectangle = destinationRectangles
-                .stream()
-                .filter((r) -> contains(r, start))
-                .findFirst()
-                .orElseThrow(() -> new IllegalArgumentException(
-                        "Center point is not inside any of the rectangles!"));
+        final List<RoundedRectangleShape> shapes = new ArrayList<>(rectangleCount);
+        final List<Animator> cornerAnimators = new ArrayList<>(rectangleCount);
+
+        RectangleWithTextSelectionLayout centerRectangle = null;
 
         int startingOffset = 0;
-        for (RectF rectangle : destinationRectangles) {
-            if (rectangle.equals(centerRectangle)) {
+        int startingRectangleIndex = 0;
+        for (int index = 0; index < rectangleCount; ++index) {
+            final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+                    destinationRectangles.get(index);
+            final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
+            if (contains(rectangle, start)) {
+                centerRectangle = rectangleWithTextSelectionLayout;
                 break;
             }
             startingOffset += rectangle.width();
+            ++startingRectangleIndex;
         }
 
-        startingOffset += start.x - centerRectangle.left;
+        if (centerRectangle == null) {
+            throw new IllegalArgumentException("Center point is not inside any of the rectangles!");
+        }
 
-        final float centerRectangleHalfHeight = centerRectangle.height() / 2;
-        final float startingOffsetLeft = startingOffset - centerRectangleHalfHeight;
-        final float startingOffsetRight = startingOffset + centerRectangleHalfHeight;
+        startingOffset += start.x - centerRectangle.getRectangle().left;
 
         final @RoundedRectangleShape.ExpansionDirection int[] expansionDirections =
                 generateDirections(centerRectangle, destinationRectangles);
 
         final @RoundedRectangleShape.RectangleBorderType int[] rectangleBorderTypes =
-                generateBorderTypes(destinationRectangles);
+                generateBorderTypes(rectangleCount);
 
-        int index = 0;
-
-        for (RectF rectangle : destinationRectangles) {
+        for (int index = 0; index < rectangleCount; ++index) {
+            final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+                    destinationRectangles.get(index);
+            final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
             final RoundedRectangleShape shape = new RoundedRectangleShape(
                     rectangle,
                     expansionDirections[index],
                     rectangleBorderTypes[index],
+                    rectangleWithTextSelectionLayout.getTextSelectionLayout()
+                            == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
                     mStrokeWidth);
             cornerAnimators.add(createCornerAnimator(shape, updateListener));
             shapes.add(shape);
-            index++;
         }
 
         final RectangleList rectangleList = new RectangleList(shapes);
         final ShapeDrawable shapeDrawable = new ShapeDrawable(rectangleList);
 
+        final float startingOffsetLeft;
+        final float startingOffsetRight;
+
+        final RoundedRectangleShape startingRectangleShape = shapes.get(startingRectangleIndex);
+        final float cornerRadius = startingRectangleShape.getCornerRadius();
+        if (startingRectangleShape.mRectangleBorderType
+                == RoundedRectangleShape.RectangleBorderType.FIT) {
+            switch (startingRectangleShape.mExpansionDirection) {
+                case RoundedRectangleShape.ExpansionDirection.LEFT:
+                    startingOffsetLeft = startingOffsetRight = startingOffset - cornerRadius / 2;
+                    break;
+                case RoundedRectangleShape.ExpansionDirection.RIGHT:
+                    startingOffsetLeft = startingOffsetRight = startingOffset + cornerRadius / 2;
+                    break;
+                case RoundedRectangleShape.ExpansionDirection.CENTER:  // fall through
+                default:
+                    startingOffsetLeft = startingOffset - cornerRadius / 2;
+                    startingOffsetRight = startingOffset + cornerRadius / 2;
+                    break;
+            }
+        } else {
+            startingOffsetLeft = startingOffsetRight = startingOffset;
+        }
+
         final Paint paint = shapeDrawable.getPaint();
         paint.setColor(mStrokeColor);
         paint.setStyle(Paint.Style.STROKE);
@@ -511,7 +598,8 @@
     }
 
     private static @RoundedRectangleShape.ExpansionDirection int[] generateDirections(
-            final RectF centerRectangle, final List<RectF> rectangles) {
+            final RectangleWithTextSelectionLayout centerRectangle,
+            final List<RectangleWithTextSelectionLayout> rectangles) {
         final @RoundedRectangleShape.ExpansionDirection int[] result = new int[rectangles.size()];
 
         final int centerRectangleIndex = rectangles.indexOf(centerRectangle);
@@ -538,8 +626,8 @@
     }
 
     private static @RoundedRectangleShape.RectangleBorderType int[] generateBorderTypes(
-            final List<RectF> rectangles) {
-        final @RoundedRectangleShape.RectangleBorderType int[] result = new int[rectangles.size()];
+            final int numberOfRectangles) {
+        final @RoundedRectangleShape.RectangleBorderType int[] result = new int[numberOfRectangles];
 
         for (int i = 1; i < result.length - 1; ++i) {
             result[i] = RoundedRectangleShape.RectangleBorderType.OVERSHOOT;
diff --git a/android/widget/Switch.java b/android/widget/Switch.java
index 2e1e963..604575f 100644
--- a/android/widget/Switch.java
+++ b/android/widget/Switch.java
@@ -248,10 +248,7 @@
                 com.android.internal.R.styleable.Switch_switchPadding, 0);
         mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
 
-        // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES.
-        // STOPSHIP if the above TODO is not done.
-        mUseFallbackLineSpacing =
-                context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT;
+        mUseFallbackLineSpacing = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.P;
 
         ColorStateList thumbTintList = a.getColorStateList(
                 com.android.internal.R.styleable.Switch_thumbTint);
diff --git a/android/widget/TabWidget.java b/android/widget/TabWidget.java
index 05f7c0a..f8b6837 100644
--- a/android/widget/TabWidget.java
+++ b/android/widget/TabWidget.java
@@ -61,7 +61,10 @@
     // This value will be set to 0 as soon as the first tab is added to TabHost.
     private int mSelectedTab = -1;
 
+    @Nullable
     private Drawable mLeftStrip;
+
+    @Nullable
     private Drawable mRightStrip;
 
     private boolean mDrawBottomStrips = true;
@@ -374,23 +377,36 @@
         final Drawable leftStrip = mLeftStrip;
         final Drawable rightStrip = mRightStrip;
 
-        leftStrip.setState(selectedChild.getDrawableState());
-        rightStrip.setState(selectedChild.getDrawableState());
+        if (leftStrip != null) {
+            leftStrip.setState(selectedChild.getDrawableState());
+        }
+        if (rightStrip != null) {
+            rightStrip.setState(selectedChild.getDrawableState());
+        }
 
         if (mStripMoved) {
             final Rect bounds = mBounds;
             bounds.left = selectedChild.getLeft();
             bounds.right = selectedChild.getRight();
             final int myHeight = getHeight();
-            leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
-                    myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
-            rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
-                    Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
+            if (leftStrip != null) {
+                leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
+                        myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
+            }
+            if (rightStrip != null) {
+                rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
+                        Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()),
+                        myHeight);
+            }
             mStripMoved = false;
         }
 
-        leftStrip.draw(canvas);
-        rightStrip.draw(canvas);
+        if (leftStrip != null) {
+            leftStrip.draw(canvas);
+        }
+        if (rightStrip != null) {
+            rightStrip.draw(canvas);
+        }
     }
 
     /**
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index efcc3a2..24ae03c 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -1256,9 +1256,7 @@
 
         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
-        // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES.
-        // STOPSHIP if the above TODO is not done.
-        mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT;
+        mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
 
         if (inputMethod != null) {
             Class<?> c;
@@ -5549,7 +5547,7 @@
     public final void setHint(CharSequence hint) {
         setHintInternal(hint);
 
-        if (isInputMethodTarget()) {
+        if (mEditor != null && isInputMethodTarget()) {
             mEditor.reportExtractedText();
         }
     }
@@ -6283,7 +6281,7 @@
             final int horizontalPadding = getCompoundPaddingLeft();
             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
 
-            if (mEditor.mCursorDrawable == null) {
+            if (mEditor.mDrawableForCursor == null) {
                 synchronized (TEMP_RECTF) {
                     /*
                      * The reason for this concern about the thickness of the
@@ -6310,7 +6308,7 @@
                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
                 }
             } else {
-                final Rect bounds = mEditor.mCursorDrawable.getBounds();
+                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
             }
@@ -6362,8 +6360,8 @@
             int bottom = mLayout.getLineBottom(lineEnd);
 
             // mEditor can be null in case selection is set programmatically.
-            if (invalidateCursor && mEditor != null && mEditor.mCursorDrawable != null) {
-                final Rect bounds = mEditor.mCursorDrawable.getBounds();
+            if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
+                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
                 top = Math.min(top, bounds.top);
                 bottom = Math.max(bottom, bounds.bottom);
             }
diff --git a/android/widget/TextViewSetTextLocalePerfTest.java b/android/widget/TextViewSetTextLocalePerfTest.java
index 7fc5e4f..e95676b 100644
--- a/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/android/widget/TextViewSetTextLocalePerfTest.java
@@ -16,27 +16,21 @@
 
 package android.widget;
 
-import android.app.Activity;
-import android.os.Bundle;
-import android.perftests.utils.PerfStatusReporter;
-import android.util.Log;
-
 import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
 import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.support.test.rule.ActivityTestRule;
-import android.support.test.InstrumentationRegistry;
 
-import java.util.Locale;
-import java.util.Collection;
-import java.util.Arrays;
-
-import org.junit.Test;
 import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
-import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/benchmarks/ZipFileReadBenchmark.java b/benchmarks/ZipFileReadBenchmark.java
new file mode 100644
index 0000000..f6125a6
--- /dev/null
+++ b/benchmarks/ZipFileReadBenchmark.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 benchmarks;
+
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Param;
+import java.io.File;
+import java.io.InputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+
+public class ZipFileReadBenchmark {
+    private File file;
+    @Param({"1024", "16384", "65536"}) int readBufferSize;
+
+    @BeforeExperiment
+    protected void setUp() throws Exception {
+        System.setProperty("java.io.tmpdir", "/data/local/tmp");
+        file = File.createTempFile(getClass().getName(), ".zip");
+        writeEntries(new ZipOutputStream(new FileOutputStream(file)), 2, 1024*1024);
+        ZipFile zipFile = new ZipFile(file);
+        for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
+            ZipEntry zipEntry = e.nextElement();
+        }
+        zipFile.close();
+    }
+
+    /**
+     * Compresses the given number of files, each of the given size, into a .zip archive.
+     */
+    protected void writeEntries(ZipOutputStream out, int entryCount, long entrySize)
+            throws IOException {
+        byte[] writeBuffer = new byte[8192];
+        Random random = new Random();
+        try {
+            for (int entry = 0; entry < entryCount; ++entry) {
+                ZipEntry ze = new ZipEntry(Integer.toHexString(entry));
+                ze.setSize(entrySize);
+                out.putNextEntry(ze);
+
+                for (long i = 0; i < entrySize; i += writeBuffer.length) {
+                    random.nextBytes(writeBuffer);
+                    int byteCount = (int) Math.min(writeBuffer.length, entrySize - i);
+                    out.write(writeBuffer, 0, byteCount);
+                }
+
+                out.closeEntry();
+            }
+        } finally {
+            out.close();
+        }
+    }
+
+    public void timeZipFileRead(int reps) throws Exception {
+        byte readBuffer[] = new byte[readBufferSize];
+        for (int i = 0; i < reps; ++i) {
+            ZipFile zipFile = new ZipFile(file);
+            for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
+                ZipEntry zipEntry = e.nextElement();
+                InputStream is = zipFile.getInputStream(zipEntry);
+                while (true) {
+                    if (is.read(readBuffer, 0, readBuffer.length) < 0) {
+                        break;
+                    }
+                }
+            }
+            zipFile.close();
+        }
+    }
+}
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index ad989de..c5c38f5 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -416,7 +416,7 @@
                     PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                             null, null);
                     params.sessionParams.setSize(
-                            PackageHelper.calculateInstalledSize(pkgLite, false,
+                            PackageHelper.calculateInstalledSize(pkgLite,
                             params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
                     System.err.println("Error: Failed to parse APK file: " + e);
@@ -636,7 +636,7 @@
             out = session.openWrite(splitName, 0, sizeBytes);
 
             int total = 0;
-            byte[] buffer = new byte[65536];
+            byte[] buffer = new byte[1024 * 1024];
             int c;
             while ((c = in.read(buffer)) != -1) {
                 total += c;
diff --git a/com/android/defcontainer/DefaultContainerService.java b/com/android/defcontainer/DefaultContainerService.java
index 3800e6f..4a771eb 100644
--- a/com/android/defcontainer/DefaultContainerService.java
+++ b/com/android/defcontainer/DefaultContainerService.java
@@ -16,8 +16,6 @@
 
 package com.android.defcontainer;
 
-import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
-
 import android.app.IntentService;
 import android.content.Context;
 import android.content.Intent;
@@ -31,21 +29,15 @@
 import android.content.res.ObbInfo;
 import android.content.res.ObbScanner;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.Environment.UserEnvironment;
-import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructStatVfs;
 import android.util.Slog;
 
 import com.android.internal.app.IMediaContainerService;
-import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.IParcelFileDescriptorFactory;
 import com.android.internal.util.ArrayUtils;
@@ -72,51 +64,6 @@
 
     private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
         /**
-         * Creates a new container and copies package there.
-         *
-         * @param packagePath absolute path to the package to be copied. Can be
-         *            a single monolithic APK file or a cluster directory
-         *            containing one or more APKs.
-         * @param containerId the id of the secure container that should be used
-         *            for creating a secure container into which the resource
-         *            will be copied.
-         * @param key Refers to key used for encrypting the secure container
-         * @return Returns the new cache path where the resource has been copied
-         *         into
-         */
-        @Override
-        public String copyPackageToContainer(String packagePath, String containerId, String key,
-                boolean isExternal, boolean isForwardLocked, String abiOverride) {
-            if (packagePath == null || containerId == null) {
-                return null;
-            }
-
-            if (isExternal) {
-                // Make sure the sdcard is mounted.
-                String status = Environment.getExternalStorageState();
-                if (!status.equals(Environment.MEDIA_MOUNTED)) {
-                    Slog.w(TAG, "Make sure sdcard is mounted.");
-                    return null;
-                }
-            }
-
-            PackageLite pkg = null;
-            NativeLibraryHelper.Handle handle = null;
-            try {
-                final File packageFile = new File(packagePath);
-                pkg = PackageParser.parsePackageLite(packageFile, 0);
-                handle = NativeLibraryHelper.Handle.create(pkg);
-                return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
-                        isForwardLocked, abiOverride);
-            } catch (PackageParserException | IOException e) {
-                Slog.w(TAG, "Failed to copy package at " + packagePath, e);
-                return null;
-            } finally {
-                IoUtils.closeQuietly(handle);
-            }
-        }
-
-        /**
          * Copy package to the target location.
          *
          * @param packagePath absolute path to the package to be copied. Can be
@@ -153,7 +100,6 @@
         public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
                 String abiOverride) {
             final Context context = DefaultContainerService.this;
-            final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
 
             PackageInfoLite ret = new PackageInfoLite();
             if (packagePath == null) {
@@ -167,7 +113,7 @@
             final long sizeBytes;
             try {
                 pkg = PackageParser.parsePackageLite(packageFile, 0);
-                sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+                sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
             } catch (PackageParserException | IOException e) {
                 Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
 
@@ -230,13 +176,13 @@
          *            containing one or more APKs.
          */
         @Override
-        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
-                String abiOverride) throws RemoteException {
+        public long calculateInstalledSize(String packagePath, String abiOverride)
+                throws RemoteException {
             final File packageFile = new File(packagePath);
             final PackageParser.PackageLite pkg;
             try {
                 pkg = PackageParser.parsePackageLite(packageFile, 0);
-                return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+                return PackageHelper.calculateInstalledSize(pkg, abiOverride);
             } catch (PackageParserException | IOException e) {
                 Slog.w(TAG, "Failed to calculate installed size: " + e);
                 return Long.MAX_VALUE;
@@ -292,60 +238,6 @@
         return mBinder;
     }
 
-    private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
-            String newCid, String key, boolean isExternal, boolean isForwardLocked,
-            String abiOverride) throws IOException {
-
-        // Calculate container size, rounding up to nearest MB and adding an
-        // extra MB for filesystem overhead
-        final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle,
-                isForwardLocked, abiOverride);
-
-        // Create new container
-        final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key,
-                Process.myUid(), isExternal);
-        if (newMountPath == null) {
-            throw new IOException("Failed to create container " + newCid);
-        }
-        final File targetDir = new File(newMountPath);
-
-        try {
-            // Copy all APKs
-            copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked);
-            if (!ArrayUtils.isEmpty(pkg.splitNames)) {
-                for (int i = 0; i < pkg.splitNames.length; i++) {
-                    copyFile(pkg.splitCodePaths[i], targetDir,
-                            "split_" + pkg.splitNames[i] + ".apk", isForwardLocked);
-                }
-            }
-
-            // Extract native code
-            final File libraryRoot = new File(targetDir, LIB_DIR_NAME);
-            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                    abiOverride);
-            if (res != PackageManager.INSTALL_SUCCEEDED) {
-                throw new IOException("Failed to extract native code, res=" + res);
-            }
-
-            if (!PackageHelper.finalizeSdDir(newCid)) {
-                throw new IOException("Failed to finalize " + newCid);
-            }
-
-            if (PackageHelper.isContainerMounted(newCid)) {
-                PackageHelper.unMountSdDir(newCid);
-            }
-
-        } catch (ErrnoException e) {
-            PackageHelper.destroySdDir(newCid);
-            throw e.rethrowAsIOException();
-        } catch (IOException e) {
-            PackageHelper.destroySdDir(newCid);
-            throw e;
-        }
-
-        return newMountPath;
-    }
-
     private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
             throws IOException, RemoteException {
         copyFile(pkg.baseCodePath, target, "base.apk");
@@ -373,28 +265,4 @@
             IoUtils.closeQuietly(in);
         }
     }
-
-    private void copyFile(String sourcePath, File targetDir, String targetName,
-            boolean isForwardLocked) throws IOException, ErrnoException {
-        final File sourceFile = new File(sourcePath);
-        final File targetFile = new File(targetDir, targetName);
-
-        Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile);
-        if (!FileUtils.copyFile(sourceFile, targetFile)) {
-            throw new IOException("Failed to copy " + sourceFile + " to " + targetFile);
-        }
-
-        if (isForwardLocked) {
-            final String publicTargetName = PackageHelper.replaceEnd(targetName,
-                    ".apk", ".zip");
-            final File publicTargetFile = new File(targetDir, publicTargetName);
-
-            PackageHelper.extractPublicFiles(sourceFile, publicTargetFile);
-
-            Os.chmod(targetFile.getAbsolutePath(), 0640);
-            Os.chmod(publicTargetFile.getAbsolutePath(), 0644);
-        } else {
-            Os.chmod(targetFile.getAbsolutePath(), 0644);
-        }
-    }
 }
diff --git a/com/android/ims/ImsCallProfile.java b/com/android/ims/ImsCallProfile.java
index 36abfc9..489c208 100644
--- a/com/android/ims/ImsCallProfile.java
+++ b/com/android/ims/ImsCallProfile.java
@@ -19,7 +19,9 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.telecom.VideoProfile;
+import android.util.Log;
 
 import com.android.internal.telephony.PhoneConstants;
 
@@ -216,6 +218,29 @@
     public int mServiceType;
     public int mCallType;
     public int mRestrictCause = CALL_RESTRICT_CAUSE_NONE;
+
+    /**
+     * Extras associated with this {@link ImsCallProfile}.
+     * <p>
+     * Valid data types include:
+     * <ul>
+     *     <li>{@link Integer} (and int)</li>
+     *     <li>{@link Long} (and long)</li>
+     *     <li>{@link Double} (and double)</li>
+     *     <li>{@link String}</li>
+     *     <li>{@code int[]}</li>
+     *     <li>{@code long[]}</li>
+     *     <li>{@code double[]}</li>
+     *     <li>{@code String[]}</li>
+     *     <li>{@link PersistableBundle}</li>
+     *     <li>{@link Boolean} (and boolean)</li>
+     *     <li>{@code boolean[]}</li>
+     *     <li>Other {@link Parcelable} classes in the {@code android.*} namespace.</li>
+     * </ul>
+     * <p>
+     * Invalid types will be removed when the {@link ImsCallProfile} is parceled for transmit across
+     * a {@link android.os.Binder}.
+     */
     public Bundle mCallExtras;
     public ImsStreamMediaProfile mMediaProfile;
 
@@ -315,16 +340,17 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        Bundle filteredExtras = maybeCleanseExtras(mCallExtras);
         out.writeInt(mServiceType);
         out.writeInt(mCallType);
-        out.writeParcelable(mCallExtras, 0);
+        out.writeBundle(filteredExtras);
         out.writeParcelable(mMediaProfile, 0);
     }
 
     private void readFromParcel(Parcel in) {
         mServiceType = in.readInt();
         mCallType = in.readInt();
-        mCallExtras = in.readParcelable(null);
+        mCallExtras = in.readBundle();
         mMediaProfile = in.readParcelable(null);
     }
 
@@ -465,6 +491,31 @@
     }
 
     /**
+     * Cleanses a {@link Bundle} to ensure that it contains only data of type:
+     * 1. Primitive data types (e.g. int, bool, and other values determined by
+     * {@link android.os.PersistableBundle#isValidType(Object)}).
+     * 2. Other Bundles.
+     * 3. {@link Parcelable} objects in the {@code android.*} namespace.
+     * @param extras the source {@link Bundle}
+     * @return where all elements are valid types the source {@link Bundle} is returned unmodified,
+     *      otherwise a copy of the {@link Bundle} with the invalid elements is returned.
+     */
+    private Bundle maybeCleanseExtras(Bundle extras) {
+        if (extras == null) {
+            return null;
+        }
+
+        int startSize = extras.size();
+        Bundle filtered = extras.filterValues();
+        int endSize = filtered.size();
+        if (startSize != endSize) {
+            Log.i(TAG, "maybeCleanseExtras: " + (startSize - endSize) + " extra values were "
+                    + "removed - only primitive types and system parcelables are permitted.");
+        }
+        return filtered;
+    }
+
+    /**
      * Determines if a video state is set in a video state bit-mask.
      *
      * @param videoState The video state bit mask.
diff --git a/com/android/internal/alsa/AlsaDevicesParser.java b/com/android/internal/alsa/AlsaDevicesParser.java
index 7cdd897..6e3d596 100644
--- a/com/android/internal/alsa/AlsaDevicesParser.java
+++ b/com/android/internal/alsa/AlsaDevicesParser.java
@@ -258,7 +258,7 @@
         return line.charAt(kIndex_CardDeviceField) == '[';
     }
 
-    public void scan() {
+    public boolean scan() {
         mDeviceRecords.clear();
 
         File devicesFile = new File(kDevicesFilePath);
@@ -274,11 +274,13 @@
                 }
             }
             reader.close();
+            return true;
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         }
+        return false;
     }
 
     //
diff --git a/com/android/internal/app/ChooserActivity.java b/com/android/internal/app/ChooserActivity.java
index 2cab009..6e0ba34 100644
--- a/com/android/internal/app/ChooserActivity.java
+++ b/com/android/internal/app/ChooserActivity.java
@@ -100,7 +100,7 @@
     private static final boolean DEBUG = false;
 
     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
-    private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
+    private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
 
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
@@ -1450,11 +1450,16 @@
                         getFirstRowPosition(rowPosition + 1));
                 int serviceSpacing = holder.row.getContext().getResources()
                         .getDimensionPixelSize(R.dimen.chooser_service_spacing);
-                int top = rowPosition == 0 ? serviceSpacing : 0;
-                if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
-                    setVertPadding(holder, top, serviceSpacing);
+                if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+                    // if the row is the only row for target service
+                    setVertPadding(holder, 0, 0);
                 } else {
-                    setVertPadding(holder, top, 0);
+                    int top = rowPosition == 0 ? serviceSpacing : 0;
+                    if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+                        setVertPadding(holder, top, serviceSpacing);
+                    } else {
+                        setVertPadding(holder, top, 0);
+                    }
                 }
             } else {
                 holder.row.setBackgroundColor(Color.TRANSPARENT);
@@ -1580,8 +1585,8 @@
                 } catch (RemoteException e) {
                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
                     mChooserActivity.unbindService(this);
-                    destroy();
                     mChooserActivity.mServiceConnections.remove(this);
+                    destroy();
                 }
             }
         }
@@ -1597,7 +1602,6 @@
                 }
 
                 mChooserActivity.unbindService(this);
-                destroy();
                 mChooserActivity.mServiceConnections.remove(this);
                 if (mChooserActivity.mServiceConnections.isEmpty()) {
                     mChooserActivity.mChooserHandler.removeMessages(
@@ -1605,6 +1609,7 @@
                     mChooserActivity.sendVoiceChoicesIfNeeded();
                 }
                 mConnectedComponent = null;
+                destroy();
             }
         }
 
diff --git a/com/android/internal/app/NightDisplayController.java b/com/android/internal/app/NightDisplayController.java
index 860c5c4..7a1383c 100644
--- a/com/android/internal/app/NightDisplayController.java
+++ b/com/android/internal/app/NightDisplayController.java
@@ -32,8 +32,12 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Calendar;
-import java.util.Locale;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
 
 /**
  * Controller for managing Night display settings.
@@ -116,8 +120,9 @@
      */
     public boolean setActivated(boolean activated) {
         if (isActivated() != activated) {
-            Secure.putLongForUser(mContext.getContentResolver(),
-                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, System.currentTimeMillis(),
+            Secure.putStringForUser(mContext.getContentResolver(),
+                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                    LocalDateTime.now().toString(),
                     mUserId);
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
@@ -128,17 +133,22 @@
      * Returns the time when Night display's activation state last changed, or {@code null} if it
      * has never been changed.
      */
-    public Calendar getLastActivatedTime() {
+    public LocalDateTime getLastActivatedTime() {
         final ContentResolver cr = mContext.getContentResolver();
-        final long lastActivatedTimeMillis = Secure.getLongForUser(
-                cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1, mUserId);
-        if (lastActivatedTimeMillis < 0) {
-            return null;
+        final String lastActivatedTime = Secure.getStringForUser(
+                cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, mUserId);
+        if (lastActivatedTime != null) {
+            try {
+                return LocalDateTime.parse(lastActivatedTime);
+            } catch (DateTimeParseException ignored) {}
+            // Uses the old epoch time.
+            try {
+                return LocalDateTime.ofInstant(
+                    Instant.ofEpochMilli(Long.parseLong(lastActivatedTime)),
+                    ZoneId.systemDefault());
+            } catch (DateTimeException|NumberFormatException ignored) {}
         }
-
-        final Calendar lastActivatedTime = Calendar.getInstance();
-        lastActivatedTime.setTimeInMillis(lastActivatedTimeMillis);
-        return lastActivatedTime;
+        return null;
     }
 
     /**
@@ -183,8 +193,10 @@
         }
 
         if (getAutoMode() != autoMode) {
-            Secure.putLongForUser(mContext.getContentResolver(),
-                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1L, mUserId);
+            Secure.putStringForUser(mContext.getContentResolver(),
+                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                    null,
+                    mUserId);
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
                 Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId);
@@ -206,7 +218,7 @@
                     R.integer.config_defaultNightDisplayCustomStartTime);
         }
 
-        return LocalTime.valueOf(startTimeValue);
+        return LocalTime.ofSecondOfDay(startTimeValue / 1000);
     }
 
     /**
@@ -221,7 +233,7 @@
             throw new IllegalArgumentException("startTime cannot be null");
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toMillis(), mUserId);
+                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toSecondOfDay() * 1000, mUserId);
     }
 
     /**
@@ -240,7 +252,7 @@
                     R.integer.config_defaultNightDisplayCustomEndTime);
         }
 
-        return LocalTime.valueOf(endTimeValue);
+        return LocalTime.ofSecondOfDay(endTimeValue / 1000);
     }
 
     /**
@@ -255,7 +267,7 @@
             throw new IllegalArgumentException("endTime cannot be null");
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId);
+                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toSecondOfDay() * 1000, mUserId);
     }
 
     /**
@@ -379,106 +391,6 @@
     }
 
     /**
-     * A time without a time-zone or date.
-     */
-    public static class LocalTime {
-
-        /**
-         * The hour of the day from 0 - 23.
-         */
-        public final int hourOfDay;
-        /**
-         * The minute within the hour from 0 - 59.
-         */
-        public final int minute;
-
-        public LocalTime(int hourOfDay, int minute) {
-            if (hourOfDay < 0 || hourOfDay > 23) {
-                throw new IllegalArgumentException("Invalid hourOfDay: " + hourOfDay);
-            } else if (minute < 0 || minute > 59) {
-                throw new IllegalArgumentException("Invalid minute: " + minute);
-            }
-
-            this.hourOfDay = hourOfDay;
-            this.minute = minute;
-        }
-
-        /**
-         * Returns the first date time corresponding to this local time that occurs before the
-         * provided date time.
-         *
-         * @param time the date time to compare against
-         * @return the prior date time corresponding to this local time
-         */
-        public Calendar getDateTimeBefore(Calendar time) {
-            final Calendar c = Calendar.getInstance();
-            c.set(Calendar.YEAR, time.get(Calendar.YEAR));
-            c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
-
-            c.set(Calendar.HOUR_OF_DAY, hourOfDay);
-            c.set(Calendar.MINUTE, minute);
-            c.set(Calendar.SECOND, 0);
-            c.set(Calendar.MILLISECOND, 0);
-
-            // Check if the local time has past, if so return the same time tomorrow.
-            if (c.after(time)) {
-                c.add(Calendar.DATE, -1);
-            }
-
-            return c;
-        }
-
-        /**
-         * Returns the first date time corresponding to this local time that occurs after the
-         * provided date time.
-         *
-         * @param time the date time to compare against
-         * @return the next date time corresponding to this local time
-         */
-        public Calendar getDateTimeAfter(Calendar time) {
-            final Calendar c = Calendar.getInstance();
-            c.set(Calendar.YEAR, time.get(Calendar.YEAR));
-            c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
-
-            c.set(Calendar.HOUR_OF_DAY, hourOfDay);
-            c.set(Calendar.MINUTE, minute);
-            c.set(Calendar.SECOND, 0);
-            c.set(Calendar.MILLISECOND, 0);
-
-            // Check if the local time has past, if so return the same time tomorrow.
-            if (c.before(time)) {
-                c.add(Calendar.DATE, 1);
-            }
-
-            return c;
-        }
-
-        /**
-         * Returns a local time corresponding the given number of milliseconds from midnight.
-         *
-         * @param millis the number of milliseconds from midnight
-         * @return the corresponding local time
-         */
-        private static LocalTime valueOf(int millis) {
-            final int hourOfDay = (millis / 3600000) % 24;
-            final int minutes = (millis / 60000) % 60;
-            return new LocalTime(hourOfDay, minutes);
-        }
-
-        /**
-         * Returns the local time represented as milliseconds from midnight.
-         */
-        private int toMillis() {
-            return hourOfDay * 3600000 + minute * 60000;
-        }
-
-        @Override
-        public String toString() {
-            return String.format(Locale.US, "%02d:%02d", hourOfDay, minute);
-        }
-    }
-
-    /**
      * Callback invoked whenever the Night display settings are changed.
      */
     public interface Callback {
diff --git a/com/android/internal/app/ShutdownActivity.java b/com/android/internal/app/ShutdownActivity.java
index 745d28f..f81e838 100644
--- a/com/android/internal/app/ShutdownActivity.java
+++ b/com/android/internal/app/ShutdownActivity.java
@@ -41,6 +41,9 @@
         mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
         mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
         mUserRequested = intent.getBooleanExtra(Intent.EXTRA_USER_REQUESTED_SHUTDOWN, false);
+        final String reason = mUserRequested
+                ? PowerManager.SHUTDOWN_USER_REQUESTED
+                : intent.getStringExtra(Intent.EXTRA_REASON);
         Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
 
         Thread thr = new Thread("ShutdownActivity") {
@@ -52,9 +55,7 @@
                     if (mReboot) {
                         pm.reboot(mConfirm, null, false);
                     } else {
-                        pm.shutdown(mConfirm,
-                                    mUserRequested ? PowerManager.SHUTDOWN_USER_REQUESTED : null,
-                                    false);
+                        pm.shutdown(mConfirm, reason, false);
                     }
                 } catch (RemoteException e) {
                 }
diff --git a/com/android/internal/app/procstats/DumpUtils.java b/com/android/internal/app/procstats/DumpUtils.java
index ebedc89..0bc8c48 100644
--- a/com/android/internal/app/procstats/DumpUtils.java
+++ b/com/android/internal/app/procstats/DumpUtils.java
@@ -29,6 +29,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import static com.android.internal.app.procstats.ProcessStats.*;
 
@@ -66,6 +67,8 @@
             "cch-activity", "cch-aclient", "cch-empty"
     };
 
+    // State enum is defined in frameworks/base/core/proto/android/service/procstats.proto
+    // Update states must sync enum definition as well, the ordering must not be changed.
     static final String[] ADJ_SCREEN_TAGS = new String[] {
             "0", "1"
     };
@@ -177,6 +180,13 @@
         printArrayEntry(pw, STATE_TAGS,  state, 1);
     }
 
+    public static void printProcStateTagProto(ProtoOutputStream proto, long screenId, long memId,
+            long stateId, int state) {
+        state = printProto(proto, screenId, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD * STATE_COUNT);
+        state = printProto(proto, memId, ADJ_MEM_TAGS, state, STATE_COUNT);
+        printProto(proto, stateId, STATE_TAGS, state, 1);
+    }
+
     public static void printAdjTag(PrintWriter pw, int state) {
         state = printArrayEntry(pw, ADJ_SCREEN_TAGS,  state, ADJ_SCREEN_MOD);
         printArrayEntry(pw, ADJ_MEM_TAGS, state, 1);
@@ -352,6 +362,15 @@
         return value - index*mod;
     }
 
+    public static int printProto(ProtoOutputStream proto, long fieldId, String[] array, int value, int mod) {
+        int index = value/mod;
+        if (index >= 0 && index < array.length) {
+            // Valid state enum number starts at 1, 0 stands for unknown.
+            proto.write(fieldId, index + 1);
+        } // else enum default is always zero in proto3
+        return value - index*mod;
+    }
+
     public static String collapseString(String pkgName, String itemName) {
         if (itemName.startsWith(pkgName)) {
             final int ITEMLEN = itemName.length();
diff --git a/com/android/internal/app/procstats/ProcessState.java b/com/android/internal/app/procstats/ProcessState.java
index e0a4053..7519fce 100644
--- a/com/android/internal/app/procstats/ProcessState.java
+++ b/com/android/internal/app/procstats/ProcessState.java
@@ -21,6 +21,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.pm.PackageProto;
+import android.service.procstats.ProcessStatsProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -29,6 +31,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.app.procstats.ProcessStats.PackageState;
@@ -69,6 +73,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 public final class ProcessState {
@@ -1157,6 +1164,7 @@
         }
     }
 
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("ProcessState{").append(Integer.toHexString(System.identityHashCode(this)))
@@ -1167,4 +1175,76 @@
         sb.append("}");
         return sb.toString();
     }
+
+    public void toProto(ProtoOutputStream proto, String procName, int uid, long now) {
+        proto.write(ProcessStatsProto.PROCESS, procName);
+        proto.write(ProcessStatsProto.UID, uid);
+        if (mNumExcessiveCpu > 0 || mNumCachedKill > 0 ) {
+            final long killToken = proto.start(ProcessStatsProto.KILL);
+            proto.write(ProcessStatsProto.Kill.CPU, mNumExcessiveCpu);
+            proto.write(ProcessStatsProto.Kill.CACHED, mNumCachedKill);
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.Kill.CACHED_PSS,
+                    mMinCachedKillPss, mAvgCachedKillPss, mMaxCachedKillPss);
+            proto.end(killToken);
+        }
+
+        // Group proc stats by type (screen state + mem state + process state)
+        Map<Integer, Long> durationByState = new HashMap<>();
+        boolean didCurState = false;
+        for (int i=0; i<mDurations.getKeyCount(); i++) {
+            final int key = mDurations.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            long time = mDurations.getValue(key);
+            if (mCurState == type) {
+                didCurState = true;
+                time += now - mStartTime;
+            }
+            durationByState.put(type, time);
+        }
+        if (!didCurState && mCurState != STATE_NOTHING) {
+            durationByState.put(mCurState, now - mStartTime);
+        }
+
+        for (int i=0; i<mPssTable.getKeyCount(); i++) {
+            final int key = mPssTable.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            if (!durationByState.containsKey(type)) {
+                // state without duration should not have stats!
+                continue;
+            }
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    type);
+
+            long duration = durationByState.get(type);
+            durationByState.remove(type); // remove the key since it is already being dumped.
+            proto.write(ProcessStatsProto.State.DURATION_MS, duration);
+
+            proto.write(ProcessStatsProto.State.SAMPLE_SIZE, mPssTable.getValue(key, PSS_SAMPLE_COUNT));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.PSS,
+                    mPssTable.getValue(key, PSS_MINIMUM),
+                    mPssTable.getValue(key, PSS_AVERAGE),
+                    mPssTable.getValue(key, PSS_MAXIMUM));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.USS,
+                    mPssTable.getValue(key, PSS_USS_MINIMUM),
+                    mPssTable.getValue(key, PSS_USS_AVERAGE),
+                    mPssTable.getValue(key, PSS_USS_MAXIMUM));
+
+            proto.end(stateToken);
+        }
+
+        for (Map.Entry<Integer, Long> entry : durationByState.entrySet()) {
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    entry.getKey());
+            proto.write(ProcessStatsProto.State.DURATION_MS, entry.getValue());
+            proto.end(stateToken);
+        }
+    }
 }
diff --git a/com/android/internal/app/procstats/ProcessStats.java b/com/android/internal/app/procstats/ProcessStats.java
index 35b53c2..14f5e5b 100644
--- a/com/android/internal/app/procstats/ProcessStats.java
+++ b/com/android/internal/app/procstats/ProcessStats.java
@@ -22,6 +22,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.procstats.ProcessStatsSectionProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -30,6 +31,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.procstats.DurationsTable;
@@ -1706,6 +1708,46 @@
         }
     }
 
+    public void toProto(ProtoOutputStream proto, long now) {
+        final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+
+        proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
+        proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
+                mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
+        proto.write(ProcessStatsSectionProto.START_UPTIME_MS, mTimePeriodStartUptime);
+        proto.write(ProcessStatsSectionProto.END_UPTIME_MS, mTimePeriodEndUptime);
+        proto.write(ProcessStatsSectionProto.RUNTIME, mRuntime);
+        proto.write(ProcessStatsSectionProto.HAS_SWAPPED_PSS, mHasSwappedOutPss);
+        boolean partial = true;
+        if ((mFlags&FLAG_SHUTDOWN) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SHUTDOWN);
+            partial = false;
+        }
+        if ((mFlags&FLAG_SYSPROPS) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SYSPROPS);
+            partial = false;
+        }
+        if ((mFlags&FLAG_COMPLETE) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_COMPLETE);
+            partial = false;
+        }
+        if (partial) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL);
+        }
+
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        for (int ip=0; ip<procMap.size(); ip++) {
+            String procName = procMap.keyAt(ip);
+            SparseArray<ProcessState> uids = procMap.valueAt(ip);
+            for (int iu=0; iu<uids.size(); iu++) {
+                final int uid = uids.keyAt(iu);
+                final ProcessState procState = uids.valueAt(iu);
+                final long processStateToken = proto.start(ProcessStatsSectionProto.PROCESS_STATS);
+                procState.toProto(proto, procName, uid, now);
+                proto.end(processStateToken);
+            }
+        }
+    }
 
     final public static class ProcessStateHolder {
         public final int appVersion;
diff --git a/com/android/internal/content/PackageHelper.java b/com/android/internal/content/PackageHelper.java
index e923223..59a7995 100644
--- a/com/android/internal/content/PackageHelper.java
+++ b/com/android/internal/content/PackageHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.content;
 
-import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
 
 import android.content.Context;
@@ -27,13 +26,11 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageParser.PackageLite;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
-import android.os.storage.StorageResultCode;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
 import android.provider.Settings;
@@ -45,15 +42,9 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
 import java.util.Objects;
 import java.util.UUID;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
 
 /**
  * Constants used internally between the PackageManager
@@ -72,7 +63,6 @@
     public static final int RECOMMEND_FAILED_INVALID_URI = -6;
     public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
 
-    private static final boolean localLOGV = false;
     private static final String TAG = "PackageHelper";
     // App installation location settings values
     public static final int APP_INSTALL_AUTO = 0;
@@ -91,259 +81,6 @@
         }
     }
 
-    public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
-            boolean isExternal) {
-        // Round up to nearest MB, plus another MB for filesystem overhead
-        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
-        try {
-            IStorageManager storageManager = getStorageManager();
-
-            if (localLOGV)
-                Log.i(TAG, "Size of container " + sizeMb + " MB");
-
-            int rc = storageManager.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
-                    isExternal);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.e(TAG, "Failed to create secure container " + cid);
-                return null;
-            }
-            String cachePath = storageManager.getSecureContainerPath(cid);
-            if (localLOGV) Log.i(TAG, "Created secure container " + cid +
-                    " at " + cachePath);
-                return cachePath;
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        return null;
-    }
-
-    public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
-        // Round up to nearest MB, plus another MB for filesystem overhead
-        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
-        try {
-            IStorageManager storageManager = getStorageManager();
-            int rc = storageManager.resizeSecureContainer(cid, sizeMb, sdEncKey);
-            if (rc == StorageResultCode.OperationSucceeded) {
-                return true;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        Log.e(TAG, "Failed to create secure container " + cid);
-        return false;
-    }
-
-    public static String mountSdDir(String cid, String key, int ownerUid) {
-        return mountSdDir(cid, key, ownerUid, true);
-    }
-
-    public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
-        try {
-            int rc = getStorageManager().mountSecureContainer(cid, key, ownerUid, readOnly);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
-                return null;
-            }
-            return getStorageManager().getSecureContainerPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        return null;
-    }
-
-   public static boolean unMountSdDir(String cid) {
-    try {
-        int rc = getStorageManager().unmountSecureContainer(cid, true);
-        if (rc != StorageResultCode.OperationSucceeded) {
-            Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
-            return false;
-        }
-        return true;
-    } catch (RemoteException e) {
-        Log.e(TAG, "StorageManagerService running?");
-    }
-        return false;
-   }
-
-   public static boolean renameSdDir(String oldId, String newId) {
-       try {
-           int rc = getStorageManager().renameSecureContainer(oldId, newId);
-           if (rc != StorageResultCode.OperationSucceeded) {
-               Log.e(TAG, "Failed to rename " + oldId + " to " +
-                       newId + "with rc " + rc);
-               return false;
-           }
-           return true;
-       } catch (RemoteException e) {
-           Log.i(TAG, "Failed ot rename  " + oldId + " to " + newId +
-                   " with exception : " + e);
-       }
-       return false;
-   }
-
-   public static String getSdDir(String cid) {
-       try {
-            return getStorageManager().getSecureContainerPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get container path for " + cid +
-                " with exception " + e);
-        }
-        return null;
-   }
-
-   public static String getSdFilesystem(String cid) {
-       try {
-            return getStorageManager().getSecureContainerFilesystemPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get container path for " + cid +
-                " with exception " + e);
-        }
-        return null;
-   }
-
-    public static boolean finalizeSdDir(String cid) {
-        try {
-            int rc = getStorageManager().finalizeSecureContainer(cid);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to finalize container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to finalize container " + cid +
-                    " with exception " + e);
-        }
-        return false;
-    }
-
-    public static boolean destroySdDir(String cid) {
-        try {
-            if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
-            int rc = getStorageManager().destroySecureContainer(cid, true);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to destroy container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to destroy container " + cid +
-                    " with exception " + e);
-        }
-        return false;
-    }
-
-    public static String[] getSecureContainerList() {
-        try {
-            return getStorageManager().getSecureContainerList();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get secure container list with exception" +
-                    e);
-        }
-        return null;
-    }
-
-   public static boolean isContainerMounted(String cid) {
-       try {
-           return getStorageManager().isSecureContainerMounted(cid);
-       } catch (RemoteException e) {
-           Log.e(TAG, "Failed to find out if container " + cid + " mounted");
-       }
-       return false;
-   }
-
-    /**
-     * Extract public files for the single given APK.
-     */
-    public static long extractPublicFiles(File apkFile, File publicZipFile)
-            throws IOException {
-        final FileOutputStream fstr;
-        final ZipOutputStream publicZipOutStream;
-
-        if (publicZipFile == null) {
-            fstr = null;
-            publicZipOutStream = null;
-        } else {
-            fstr = new FileOutputStream(publicZipFile);
-            publicZipOutStream = new ZipOutputStream(fstr);
-            Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
-        }
-
-        long size = 0L;
-
-        try {
-            final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
-            try {
-                // Copy manifest, resources.arsc and res directory to public zip
-                for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
-                    final String zipEntryName = zipEntry.getName();
-                    if ("AndroidManifest.xml".equals(zipEntryName)
-                            || "resources.arsc".equals(zipEntryName)
-                            || zipEntryName.startsWith("res/")) {
-                        size += zipEntry.getSize();
-                        if (publicZipFile != null) {
-                            copyZipEntry(zipEntry, privateZip, publicZipOutStream);
-                        }
-                    }
-                }
-            } finally {
-                try { privateZip.close(); } catch (IOException e) {}
-            }
-
-            if (publicZipFile != null) {
-                publicZipOutStream.finish();
-                publicZipOutStream.flush();
-                FileUtils.sync(fstr);
-                publicZipOutStream.close();
-                FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
-                        | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
-            }
-        } finally {
-            IoUtils.closeQuietly(publicZipOutStream);
-        }
-
-        return size;
-    }
-
-    private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
-            ZipOutputStream outZipStream) throws IOException {
-        byte[] buffer = new byte[4096];
-        int num;
-
-        ZipEntry newEntry;
-        if (zipEntry.getMethod() == ZipEntry.STORED) {
-            // Preserve the STORED method of the input entry.
-            newEntry = new ZipEntry(zipEntry);
-        } else {
-            // Create a new entry so that the compressed len is recomputed.
-            newEntry = new ZipEntry(zipEntry.getName());
-        }
-        outZipStream.putNextEntry(newEntry);
-
-        final InputStream data = inZipFile.getInputStream(zipEntry);
-        try {
-            while ((num = data.read(buffer)) > 0) {
-                outZipStream.write(buffer, 0, num);
-            }
-            outZipStream.flush();
-        } finally {
-            IoUtils.closeQuietly(data);
-        }
-    }
-
-    public static boolean fixSdPermissions(String cid, int gid, String filename) {
-        try {
-            int rc = getStorageManager().fixPermissionsSecureContainer(cid, gid, filename);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to fixperms container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
-        }
-        return false;
-    }
-
     /**
      * A group of external dependencies used in
      * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
@@ -638,29 +375,37 @@
         return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
     }
 
+    @Deprecated
     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
             String abiOverride) throws IOException {
+        return calculateInstalledSize(pkg, abiOverride);
+    }
+
+    public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
+            throws IOException {
         NativeLibraryHelper.Handle handle = null;
         try {
             handle = NativeLibraryHelper.Handle.create(pkg);
-            return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
+            return calculateInstalledSize(pkg, handle, abiOverride);
         } finally {
             IoUtils.closeQuietly(handle);
         }
     }
 
+    @Deprecated
+    public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
+            NativeLibraryHelper.Handle handle, String abiOverride) throws IOException {
+        return calculateInstalledSize(pkg, handle, abiOverride);
+    }
+
     public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
-            boolean isForwardLocked, String abiOverride) throws IOException {
+            String abiOverride) throws IOException {
         long sizeBytes = 0;
 
         // Include raw APKs, and possibly unpacked resources
         for (String codePath : pkg.getAllCodePaths()) {
             final File codeFile = new File(codePath);
             sizeBytes += codeFile.length();
-
-            if (isForwardLocked) {
-                sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
-            }
         }
 
         // Include all relevant native code
diff --git a/com/android/internal/os/BatteryStatsHelper.java b/com/android/internal/os/BatteryStatsHelper.java
index f085e29..15dc6f5 100644
--- a/com/android/internal/os/BatteryStatsHelper.java
+++ b/com/android/internal/os/BatteryStatsHelper.java
@@ -143,6 +143,9 @@
     public static boolean checkWifiOnly(Context context) {
         ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
+        if (cm == null) {
+            return false;
+        }
         return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
     }
 
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index 0bd2981..36fd991 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -119,7 +119,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 166 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 167 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -341,8 +341,8 @@
     protected final TimeBase mOnBatteryTimeBase = new TimeBase();
 
     // These are the objects that will want to do something when the device
-    // is unplugged from power *and* the screen is off.
-    final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
+    // is unplugged from power *and* the screen is off or doze.
+    protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
 
     // Set to true when we want to distribute CPU across wakelocks for the next
     // CPU update, even if we aren't currently running wake locks.
@@ -436,8 +436,12 @@
     public boolean mRecordAllHistory;
     boolean mNoAutoReset;
 
-    int mScreenState = Display.STATE_UNKNOWN;
-    StopwatchTimer mScreenOnTimer;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected int mScreenState = Display.STATE_UNKNOWN;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mScreenOnTimer;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mScreenDozeTimer;
 
     int mScreenBrightnessBin = -1;
     final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -583,12 +587,16 @@
     int mHighDischargeAmountSinceCharge;
     int mDischargeScreenOnUnplugLevel;
     int mDischargeScreenOffUnplugLevel;
+    int mDischargeScreenDozeUnplugLevel;
     int mDischargeAmountScreenOn;
     int mDischargeAmountScreenOnSinceCharge;
     int mDischargeAmountScreenOff;
     int mDischargeAmountScreenOffSinceCharge;
+    int mDischargeAmountScreenDoze;
+    int mDischargeAmountScreenDozeSinceCharge;
 
     private LongSamplingCounter mDischargeScreenOffCounter;
+    private LongSamplingCounter mDischargeScreenDozeCounter;
     private LongSamplingCounter mDischargeCounter;
 
     static final int MAX_LEVEL_STEPS = 200;
@@ -673,13 +681,18 @@
     }
 
     @Override
-    public LongCounter getDischargeScreenOffCoulombCounter() {
-        return mDischargeScreenOffCounter;
+    public long getMahDischarge(int which) {
+        return mDischargeCounter.getCountLocked(which);
     }
 
     @Override
-    public LongCounter getDischargeCoulombCounter() {
-        return mDischargeCounter;
+    public long getMahDischargeScreenOff(int which) {
+        return mDischargeScreenOffCounter.getCountLocked(which);
+    }
+
+    @Override
+    public long getMahDischargeScreenDoze(int which) {
+        return mDischargeScreenDozeCounter.getCountLocked(which);
     }
 
     @Override
@@ -3573,8 +3586,9 @@
         mActiveHistoryStates2 = 0xffffffff;
     }
 
-    public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
+    public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime,
             long realtime) {
+        final boolean screenOff = isScreenOff(screenState) || isScreenDoze(screenState);
         final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning();
         final boolean updateOnBatteryScreenOffTimeBase =
                 (unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning();
@@ -3591,20 +3605,22 @@
                 updateRpmStatsLocked(); // if either OnBattery or OnBatteryScreenOff timebase changes.
             }
             if (DEBUG_ENERGY_CPU) {
-                Slog.d(TAG, "Updating cpu time because screen is now " + (screenOff ? "off" : "on")
+                Slog.d(TAG, "Updating cpu time because screen is now "
+                        + Display.stateToString(screenState)
                         + " and battery is " + (unplugged ? "on" : "off"));
             }
             updateCpuTimeLocked();
 
             mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
-            mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
-            for (int i = mUidStats.size() - 1; i >= 0; --i) {
-                final Uid u = mUidStats.valueAt(i);
-                if (updateOnBatteryTimeBase) {
-                    u.updateOnBatteryBgTimeBase(uptime, realtime);
+            if (updateOnBatteryTimeBase) {
+                for (int i = mUidStats.size() - 1; i >= 0; --i) {
+                    mUidStats.valueAt(i).updateOnBatteryBgTimeBase(uptime, realtime);
                 }
-                if (updateOnBatteryScreenOffTimeBase) {
-                    u.updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
+            }
+            if (updateOnBatteryScreenOffTimeBase) {
+                mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
+                for (int i = mUidStats.size() - 1; i >= 0; --i) {
+                    mUidStats.valueAt(i).updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
                 }
             }
         }
@@ -3864,8 +3880,10 @@
     }
 
     public void setPretendScreenOff(boolean pretendScreenOff) {
-        mPretendScreenOff = pretendScreenOff;
-        noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+        if (mPretendScreenOff != pretendScreenOff) {
+            mPretendScreenOff = pretendScreenOff;
+            noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+        }
     }
 
     private String mInitialAcquireWakeName;
@@ -4195,54 +4213,58 @@
                 }
             }
 
-            if (state == Display.STATE_ON) {
-                // Screen turning on.
-                final long elapsedRealtime = mClocks.elapsedRealtime();
-                final long uptime = mClocks.uptimeMillis();
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+
+            boolean updateHistory = false;
+            if (isScreenDoze(state)) {
+                mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mScreenDozeTimer.startRunningLocked(elapsedRealtime);
+                updateHistory = true;
+            } else if (isScreenDoze(oldState)) {
+                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mScreenDozeTimer.stopRunningLocked(elapsedRealtime);
+                updateHistory = true;
+            }
+            if (isScreenOn(state)) {
                 mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
                 if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
                         + Integer.toHexString(mHistoryCur.states));
-                addHistoryRecordLocked(elapsedRealtime, uptime);
                 mScreenOnTimer.startRunningLocked(elapsedRealtime);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime);
                 }
-
-                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false,
-                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
-                // Fake a wake lock, so we consider the device waked as long
-                // as the screen is on.
-                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
-                        elapsedRealtime, uptime);
-
-                // Update discharge amounts.
-                if (mOnBatteryInternal) {
-                    updateDischargeScreenLevelsLocked(false, true);
-                }
-            } else if (oldState == Display.STATE_ON) {
-                // Screen turning off or dozing.
-                final long elapsedRealtime = mClocks.elapsedRealtime();
-                final long uptime = mClocks.uptimeMillis();
+                updateHistory = true;
+            } else if (isScreenOn(oldState)) {
                 mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
                 if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
                         + Integer.toHexString(mHistoryCur.states));
-                addHistoryRecordLocked(elapsedRealtime, uptime);
                 mScreenOnTimer.stopRunningLocked(elapsedRealtime);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
                 }
-
+                updateHistory = true;
+            }
+            if (updateHistory) {
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
+                        + Display.stateToString(state));
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            }
+            if (isScreenOn(state)) {
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
+                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
+                // Fake a wake lock, so we consider the device waked as long as the screen is on.
+                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+                        elapsedRealtime, uptime);
+            } else if (isScreenOn(oldState)) {
                 noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
                         elapsedRealtime, uptime);
-
-                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
                         mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
-                // Update discharge amounts.
-                if (mOnBatteryInternal) {
-                    updateDischargeScreenLevelsLocked(true, false);
-                }
+            }
+            // Update discharge amounts.
+            if (mOnBatteryInternal) {
+                updateDischargeScreenLevelsLocked(oldState, state);
             }
         }
     }
@@ -5391,6 +5413,14 @@
         return mScreenOnTimer.getCountLocked(which);
     }
 
+    @Override public long getScreenDozeTime(long elapsedRealtimeUs, int which) {
+        return mScreenDozeTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getScreenDozeCount(int which) {
+        return mScreenDozeTimer.getCountLocked(which);
+    }
+
     @Override public long getScreenBrightnessTime(int brightnessBin,
             long elapsedRealtimeUs, int which) {
         return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
@@ -8829,6 +8859,7 @@
         mHandler = new MyHandler(handler.getLooper());
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
+        mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
                     mOnBatteryTimeBase);
@@ -8887,6 +8918,7 @@
         mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase);
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
         mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
+        mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mOnBattery = mOnBatteryInternal = false;
         long uptime = mClocks.uptimeMillis() * 1000;
@@ -9430,8 +9462,16 @@
         return mCharging;
     }
 
-    public boolean isScreenOn() {
-        return mScreenState == Display.STATE_ON;
+    public boolean isScreenOn(int state) {
+        return state == Display.STATE_ON;
+    }
+
+    public boolean isScreenOff(int state) {
+        return state == Display.STATE_OFF;
+    }
+
+    public boolean isScreenDoze(int state) {
+        return state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND;
     }
 
     void initTimes(long uptime, long realtime) {
@@ -9451,9 +9491,12 @@
         mDischargeAmountScreenOnSinceCharge = 0;
         mDischargeAmountScreenOff = 0;
         mDischargeAmountScreenOffSinceCharge = 0;
+        mDischargeAmountScreenDoze = 0;
+        mDischargeAmountScreenDozeSinceCharge = 0;
         mDischargeStepTracker.init();
         mChargeStepTracker.init();
         mDischargeScreenOffCounter.reset(false);
+        mDischargeScreenDozeCounter.reset(false);
         mDischargeCounter.reset(false);
     }
 
@@ -9471,15 +9514,22 @@
         mOnBatteryTimeBase.reset(uptime, realtime);
         mOnBatteryScreenOffTimeBase.reset(uptime, realtime);
         if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
-            if (mScreenState == Display.STATE_ON) {
+            if (isScreenOn(mScreenState)) {
                 mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+                mDischargeScreenDozeUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else if (isScreenDoze(mScreenState)) {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
             }
             mDischargeAmountScreenOn = 0;
             mDischargeAmountScreenOff = 0;
+            mDischargeAmountScreenDoze = 0;
         }
         initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
     }
@@ -9490,6 +9540,7 @@
         mStartCount = 0;
         initTimes(uptimeMillis * 1000, elapsedRealtimeMillis * 1000);
         mScreenOnTimer.reset(false);
+        mScreenDozeTimer.reset(false);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].reset(false);
         }
@@ -9626,33 +9677,52 @@
         }
     }
 
-    void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) {
-        if (oldScreenOn) {
+    void updateDischargeScreenLevelsLocked(int oldState, int newState) {
+        updateOldDischargeScreenLevelLocked(oldState);
+        updateNewDischargeScreenLevelLocked(newState);
+    }
+
+    private void updateOldDischargeScreenLevelLocked(int state) {
+        if (isScreenOn(state)) {
             int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
             if (diff > 0) {
                 mDischargeAmountScreenOn += diff;
                 mDischargeAmountScreenOnSinceCharge += diff;
             }
-        } else {
+        } else if (isScreenDoze(state)) {
+            int diff = mDischargeScreenDozeUnplugLevel - mDischargeCurrentLevel;
+            if (diff > 0) {
+                mDischargeAmountScreenDoze += diff;
+                mDischargeAmountScreenDozeSinceCharge += diff;
+            }
+        } else if (isScreenOff(state)){
             int diff = mDischargeScreenOffUnplugLevel - mDischargeCurrentLevel;
             if (diff > 0) {
                 mDischargeAmountScreenOff += diff;
                 mDischargeAmountScreenOffSinceCharge += diff;
             }
         }
-        if (newScreenOn) {
+    }
+
+    private void updateNewDischargeScreenLevelLocked(int state) {
+        if (isScreenOn(state)) {
             mDischargeScreenOnUnplugLevel = mDischargeCurrentLevel;
             mDischargeScreenOffUnplugLevel = 0;
-        } else {
+            mDischargeScreenDozeUnplugLevel = 0;
+        } else if (isScreenDoze(state)){
             mDischargeScreenOnUnplugLevel = 0;
+            mDischargeScreenDozeUnplugLevel = mDischargeCurrentLevel;
+            mDischargeScreenOffUnplugLevel = 0;
+        } else if (isScreenOff(state)) {
+            mDischargeScreenOnUnplugLevel = 0;
+            mDischargeScreenDozeUnplugLevel = 0;
             mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel;
         }
     }
 
     public void pullPendingStateUpdatesLocked() {
         if (mOnBatteryInternal) {
-            final boolean screenOn = mScreenState == Display.STATE_ON;
-            updateDischargeScreenLevelsLocked(screenOn, screenOn);
+            updateDischargeScreenLevelsLocked(mScreenState, mScreenState);
         }
     }
 
@@ -10785,8 +10855,8 @@
         return false;
     }
 
-    void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
-            final int oldStatus, final int level, final int chargeUAh) {
+    protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
+            final boolean onBattery, final int oldStatus, final int level, final int chargeUAh) {
         boolean doWrite = false;
         Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
         m.arg1 = onBattery ? 1 : 0;
@@ -10794,7 +10864,7 @@
 
         final long uptime = mSecUptime * 1000;
         final long realtime = mSecRealtime * 1000;
-        final boolean screenOn = mScreenState == Display.STATE_ON;
+        final int screenState = mScreenState;
         if (onBattery) {
             // We will reset our status if we are unplugging after the
             // battery was last full, or the level is at 100, or
@@ -10870,16 +10940,23 @@
             }
             addHistoryRecordLocked(mSecRealtime, mSecUptime);
             mDischargeCurrentLevel = mDischargeUnplugLevel = level;
-            if (screenOn) {
+            if (isScreenOn(screenState)) {
                 mDischargeScreenOnUnplugLevel = level;
+                mDischargeScreenDozeUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else if (isScreenDoze(screenState)) {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = level;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = level;
             }
             mDischargeAmountScreenOn = 0;
+            mDischargeAmountScreenDoze = 0;
             mDischargeAmountScreenOff = 0;
-            updateTimeBasesLocked(true, !screenOn, uptime, realtime);
+            updateTimeBasesLocked(true, screenState, uptime, realtime);
         } else {
             mLastChargingStateLevel = level;
             mOnBattery = mOnBatteryInternal = false;
@@ -10894,8 +10971,8 @@
                 mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
                 mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
             }
-            updateDischargeScreenLevelsLocked(screenOn, screenOn);
-            updateTimeBasesLocked(false, !screenOn, uptime, realtime);
+            updateDischargeScreenLevelsLocked(screenState, screenState);
+            updateTimeBasesLocked(false, screenState, uptime, realtime);
             mChargeStepTracker.init();
             mLastChargeStepLevel = level;
             mMaxChargeStepLevel = level;
@@ -11012,6 +11089,9 @@
                 final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
                 mDischargeCounter.addCountLocked(chargeDiff);
                 mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+                if (isScreenDoze(mScreenState)) {
+                    mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+                }
             }
             mHistoryCur.batteryChargeUAh = chargeUAh;
             setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11054,6 +11134,9 @@
                     final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
                     mDischargeCounter.addCountLocked(chargeDiff);
                     mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+                    if (isScreenDoze(mScreenState)) {
+                        mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+                    }
                 }
                 mHistoryCur.batteryChargeUAh = chargeUAh;
                 changed = true;
@@ -11362,10 +11445,11 @@
         return dischargeAmount;
     }
 
+    @Override
     public int getDischargeAmountScreenOn() {
         synchronized(this) {
             int val = mDischargeAmountScreenOn;
-            if (mOnBattery && mScreenState == Display.STATE_ON
+            if (mOnBattery && isScreenOn(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
                 val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
             }
@@ -11373,10 +11457,11 @@
         }
     }
 
+    @Override
     public int getDischargeAmountScreenOnSinceCharge() {
         synchronized(this) {
             int val = mDischargeAmountScreenOnSinceCharge;
-            if (mOnBattery && mScreenState == Display.STATE_ON
+            if (mOnBattery && isScreenOn(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
                 val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
             }
@@ -11384,23 +11469,51 @@
         }
     }
 
+    @Override
     public int getDischargeAmountScreenOff() {
         synchronized(this) {
             int val = mDischargeAmountScreenOff;
-            if (mOnBattery && mScreenState != Display.STATE_ON
+            if (mOnBattery && isScreenOff(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
                 val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
             }
+            // For backward compatibility, doze discharge is counted into screen off.
+            return val + getDischargeAmountScreenDoze();
+        }
+    }
+
+    @Override
+    public int getDischargeAmountScreenOffSinceCharge() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenOffSinceCharge;
+            if (mOnBattery && isScreenOff(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
+                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            }
+            // For backward compatibility, doze discharge is counted into screen off.
+            return val + getDischargeAmountScreenDozeSinceCharge();
+        }
+    }
+
+    @Override
+    public int getDischargeAmountScreenDoze() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenDoze;
+            if (mOnBattery && isScreenDoze(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+                val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
+            }
             return val;
         }
     }
 
-    public int getDischargeAmountScreenOffSinceCharge() {
+    @Override
+    public int getDischargeAmountScreenDozeSinceCharge() {
         synchronized(this) {
-            int val = mDischargeAmountScreenOffSinceCharge;
-            if (mOnBattery && mScreenState != Display.STATE_ON
-                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
-                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            int val = mDischargeAmountScreenDozeSinceCharge;
+            if (mOnBattery && isScreenDoze(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+                val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
             }
             return val;
         }
@@ -11759,12 +11872,14 @@
         mHighDischargeAmountSinceCharge = in.readInt();
         mDischargeAmountScreenOnSinceCharge = in.readInt();
         mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeAmountScreenDozeSinceCharge = in.readInt();
         mDischargeStepTracker.readFromParcel(in);
         mChargeStepTracker.readFromParcel(in);
         mDailyDischargeStepTracker.readFromParcel(in);
         mDailyChargeStepTracker.readFromParcel(in);
         mDischargeCounter.readSummaryFromParcelLocked(in);
         mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
+        mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
         int NPKG = in.readInt();
         if (NPKG > 0) {
             mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -11787,6 +11902,7 @@
 
         mScreenState = Display.STATE_UNKNOWN;
         mScreenOnTimer.readSummaryFromParcelLocked(in);
+        mScreenDozeTimer.readSummaryFromParcelLocked(in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].readSummaryFromParcelLocked(in);
         }
@@ -12180,12 +12296,14 @@
         out.writeInt(getHighDischargeAmountSinceCharge());
         out.writeInt(getDischargeAmountScreenOnSinceCharge());
         out.writeInt(getDischargeAmountScreenOffSinceCharge());
+        out.writeInt(getDischargeAmountScreenDozeSinceCharge());
         mDischargeStepTracker.writeToParcel(out);
         mChargeStepTracker.writeToParcel(out);
         mDailyDischargeStepTracker.writeToParcel(out);
         mDailyChargeStepTracker.writeToParcel(out);
         mDischargeCounter.writeSummaryFromParcelLocked(out);
         mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
+        mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
         if (mDailyPackageChanges != null) {
             final int NPKG = mDailyPackageChanges.size();
             out.writeInt(NPKG);
@@ -12203,6 +12321,7 @@
         out.writeLong(mNextMaxDailyDeadline);
 
         mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mScreenDozeTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         }
@@ -12635,6 +12754,7 @@
 
         mScreenState = Display.STATE_UNKNOWN;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
+        mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
                     mOnBatteryTimeBase, in);
@@ -12728,10 +12848,13 @@
         mDischargeAmountScreenOnSinceCharge = in.readInt();
         mDischargeAmountScreenOff = in.readInt();
         mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeAmountScreenDoze = in.readInt();
+        mDischargeAmountScreenDozeSinceCharge = in.readInt();
         mDischargeStepTracker.readFromParcel(in);
         mChargeStepTracker.readFromParcel(in);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
-        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
+        mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mLastWriteTime = in.readLong();
 
         mRpmStats.clear();
@@ -12848,6 +12971,7 @@
         mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
 
         mScreenOnTimer.writeToParcel(out, uSecRealtime);
+        mScreenDozeTimer.writeToParcel(out, uSecRealtime);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
         }
@@ -12910,10 +13034,13 @@
         out.writeInt(mDischargeAmountScreenOnSinceCharge);
         out.writeInt(mDischargeAmountScreenOff);
         out.writeInt(mDischargeAmountScreenOffSinceCharge);
+        out.writeInt(mDischargeAmountScreenDoze);
+        out.writeInt(mDischargeAmountScreenDozeSinceCharge);
         mDischargeStepTracker.writeToParcel(out);
         mChargeStepTracker.writeToParcel(out);
         mDischargeCounter.writeToParcel(out);
         mDischargeScreenOffCounter.writeToParcel(out);
+        mDischargeScreenDozeCounter.writeToParcel(out);
         out.writeLong(mLastWriteTime);
 
         out.writeInt(mRpmStats.size());
@@ -13020,8 +13147,10 @@
             pw.println("mOnBatteryScreenOffTimeBase:");
             mOnBatteryScreenOffTimeBase.dump(pw, "  ");
             Printer pr = new PrintWriterPrinter(pw);
-            pr.println("*** Screen timer:");
+            pr.println("*** Screen on timer:");
             mScreenOnTimer.logState(pr, "  ");
+            pr.println("*** Screen doze timer:");
+            mScreenDozeTimer.logState(pr, "  ");
             for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
                 pr.println("*** Screen brightness #" + i + ":");
                 mScreenBrightnessTimer[i].logState(pr, "  ");
diff --git a/com/android/internal/os/Zygote.java b/com/android/internal/os/Zygote.java
index 5ee0918..cbc63cf 100644
--- a/com/android/internal/os/Zygote.java
+++ b/com/android/internal/os/Zygote.java
@@ -49,6 +49,11 @@
     /** Make the code Java debuggable by turning off some optimizations. */
     public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
 
+    /** Turn off the verifier. */
+    public static final int DISABLE_VERIFIER = 1 << 9;
+    /** Only use oat files located in /system. Otherwise use dex/jar/apk . */
+    public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
diff --git a/com/android/internal/telephony/CallForwardInfo.java b/com/android/internal/telephony/CallForwardInfo.java
index dccf306..e40028f 100644
--- a/com/android/internal/telephony/CallForwardInfo.java
+++ b/com/android/internal/telephony/CallForwardInfo.java
@@ -16,12 +16,16 @@
 
 package com.android.internal.telephony;
 
+import android.telecom.Log;
+
 /**
  * See also RIL_CallForwardInfo in include/telephony/ril.h
  *
  * {@hide}
  */
 public class CallForwardInfo {
+    private static final String TAG = "CallForwardInfo";
+
     public int             status;      /*1 = active, 0 = not active */
     public int             reason;      /* from TS 27.007 7.11 "reason" */
     public int             serviceClass; /* Saum of CommandsInterface.SERVICE_CLASS */
@@ -31,9 +35,9 @@
 
     @Override
     public String toString() {
-        return super.toString() + (status == 0 ? " not active " : " active ")
-            + " reason: " + reason
-            + " serviceClass: " + serviceClass + " " + timeSeconds + " seconds";
-
+        return "[CallForwardInfo: status=" + (status == 0 ? " not active " : " active ")
+                + ", reason= " + reason
+                + ", serviceClass= " + serviceClass + ", timeSec= " + timeSeconds + " seconds"
+                + ", number=" + Log.pii(number) + "]";
     }
 }
diff --git a/com/android/internal/telephony/CarrierKeyDownloadManager.java b/com/android/internal/telephony/CarrierKeyDownloadManager.java
index bca337d..606f7ff 100644
--- a/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static android.preference.PreferenceManager.getDefaultSharedPreferences;
-
 import android.app.AlarmManager;
 import android.app.DownloadManager;
 import android.app.PendingIntent;
@@ -34,22 +32,30 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.Base64;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.org.bouncycastle.util.io.pem.PemReader;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.Reader;
+import java.security.PublicKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.util.Date;
 
+import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+
 /**
  * This class contains logic to get Certificates and keep them current.
  * The class will be instantiated by various Phone implementations.
@@ -68,16 +74,19 @@
     private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
             "com.android.internal.telephony.carrier_key_download_alarm";
 
-    private int mKeyAvailability = 0;
+    @VisibleForTesting
+    public int mKeyAvailability = 0;
 
     public static final String MNC = "MNC";
     public static final String MCC = "MCC";
     private static final String SEPARATOR = ":";
 
-    private static final String JSON_KEY = "key";
-    private static final String JSON_TYPE = "type";
-    private static final String JSON_IDENTIFIER = "identifier";
-    private static final String JSON_EXPIRATION_DATE = "expiration-date";
+    private static final String JSON_CERTIFICATE = "certificate";
+    // This is a hack to accomodate Verizon. Verizon 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";
+    private static final String JSON_IDENTIFIER = "key-identifier";
     private static final String JSON_CARRIER_KEYS = "carrier-keys";
     private static final String JSON_TYPE_VALUE_WLAN = "WLAN";
     private static final String JSON_TYPE_VALUE_EPDG = "EPDG";
@@ -89,7 +98,7 @@
 
     private final Phone mPhone;
     private final Context mContext;
-    private final DownloadManager mDownloadManager;
+    public final DownloadManager mDownloadManager;
     private String mURL;
 
     public CarrierKeyDownloadManager(Phone phone) {
@@ -173,14 +182,11 @@
     }
 
     /**
-     * this method resets the alarm. Starts by cleaning up the existing alarms.
-     * We look at the earliest expiration date, and setup an alarms X days prior.
-     * If the expiration date is in the past, we'll setup an alarm to run the next day. This
-     * could happen if the download has failed.
+     * this method returns the date to be used to decide on when to start downloading the key.
+     * from the carrier.
      **/
-    private void resetRenewalAlarm() {
-        cleanupRenewalAlarms();
-        int slotId = mPhone.getPhoneId();
+    @VisibleForTesting
+    public long getExpirationDate()  {
         long minExpirationDate = Long.MAX_VALUE;
         for (int key_type : CARRIER_KEY_TYPES) {
             if (!isKeyEnabled(key_type)) {
@@ -204,6 +210,20 @@
         } else {
             minExpirationDate = minExpirationDate - DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
         }
+        return minExpirationDate;
+    }
+
+    /**
+     * this method resets the alarm. Starts by cleaning up the existing alarms.
+     * We look at the earliest expiration date, and setup an alarms X days prior.
+     * If the expiration date is in the past, we'll setup an alarm to run the next day. This
+     * could happen if the download has failed.
+     **/
+    @VisibleForTesting
+    public void resetRenewalAlarm() {
+        cleanupRenewalAlarms();
+        int slotId = mPhone.getPhoneId();
+        long minExpirationDate = getExpirationDate();
         Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
         final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
@@ -225,21 +245,30 @@
     }
 
     /**
+     * Returns the sim operator.
+     **/
+    @VisibleForTesting
+    public String getSimOperator() {
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        return telephonyManager.getSimOperator(mPhone.getSubId());
+    }
+
+    /**
      *  checks if the download was sent by this particular instance. We do this by including the
      *  slot id in the key. If no value is found, we know that the download was not for this
      *  instance of the phone.
      **/
-    private boolean isValidDownload(String mccMnc) {
+    @VisibleForTesting
+    public boolean isValidDownload(String mccMnc) {
         String mccCurrent = "";
         String mncCurrent = "";
         String mccSource = "";
         String mncSource = "";
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
 
-        if (TextUtils.isEmpty(networkOperator) || TextUtils.isEmpty(mccMnc)) {
-            Log.e(LOG_TAG, "networkOperator or mcc/mnc is empty");
+        String simOperator = getSimOperator();
+        if (TextUtils.isEmpty(simOperator) || TextUtils.isEmpty(mccMnc)) {
+            Log.e(LOG_TAG, "simOperator or mcc/mnc is empty");
             return false;
         }
 
@@ -248,8 +277,8 @@
         mncSource = splitValue[1];
         Log.d(LOG_TAG, "values from sharedPrefs mcc, mnc: " + mccSource + "," + mncSource);
 
-        mccCurrent = networkOperator.substring(0, 3);
-        mncCurrent = networkOperator.substring(3);
+        mccCurrent = simOperator.substring(0, 3);
+        mncCurrent = simOperator.substring(3);
         Log.d(LOG_TAG, "using values for mcc, mnc: " + mccCurrent + "," + mncCurrent);
 
         if (TextUtils.equals(mncSource, mncCurrent) &&  TextUtils.equals(mccSource, mccCurrent)) {
@@ -348,19 +377,20 @@
      * Converts the string into a json object to retreive the nodes. The Json should have 3 nodes,
      * including the Carrier public key, the key type and the key identifier. Once the nodes have
      * been extracted, they get persisted to the database. Sample:
-     *      "carrier-keys": [ { "key": "",
-     *                         "type": WLAN,
-     *                         "identifier": "",
-     *                         "expiration-date": 1502577746000
+     *      "carrier-keys": [ { "certificate": "",
+     *                         "key-type": "WLAN",
+     *                         "key-identifier": ""
      *                        } ]
      * @param jsonStr the json string.
-     * @param mccMnc contains the mcc, mnc
+     * @param mccMnc contains the mcc, mnc.
      */
-    private void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
+    @VisibleForTesting
+    public void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
         if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)) {
             Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty");
             return;
         }
+        PemReader reader = null;
         try {
             String mcc = "";
             String mnc = "";
@@ -369,10 +399,16 @@
             mnc = splitValue[1];
             JSONObject jsonObj = new JSONObject(jsonStr);
             JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
-
             for (int i = 0; i < keys.length(); i++) {
                 JSONObject key = keys.getJSONObject(i);
-                String carrierKey = key.getString(JSON_KEY);
+                // This is a hack to accomodate Verizon. Verizon insists 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)) {
+                    cert = key.getString(JSON_CERTIFICATE);
+                } else {
+                    cert = key.getString(JSON_CERTIFICATE_ALTERNATE);
+                }
                 String typeString = key.getString(JSON_TYPE);
                 int type = UNINITIALIZED_KEY_TYPE;
                 if (typeString.equals(JSON_TYPE_VALUE_WLAN)) {
@@ -380,13 +416,27 @@
                 } else if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
                     type = TelephonyManager.KEY_TYPE_EPDG;
                 }
-                long expiration_date = key.getLong(JSON_EXPIRATION_DATE);
                 String identifier = key.getString(JSON_IDENTIFIER);
-                savePublicKey(carrierKey, type, identifier, expiration_date,
-                        mcc, mnc);
+                ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes());
+                Reader fReader = new BufferedReader(new InputStreamReader(inStream));
+                reader = new PemReader(fReader);
+                Pair<PublicKey, Long> keyInfo =
+                        getKeyInformation(reader.readPemObject().getContent());
+                reader.close();
+                savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc);
             }
         } catch (final JSONException e) {
             Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+        } catch (final Exception e) {
+            Log.e(LOG_TAG, "Exception getting certificate: " + e);
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (final Exception e) {
+                Log.e(LOG_TAG, "Exception getting certificate: " + e);
+            }
         }
     }
 
@@ -394,8 +444,8 @@
      * introspects the mKeyAvailability bitmask
      * @return true if the digit at position k is 1, else false.
      */
-
-    private boolean isKeyEnabled(int keyType) {
+    @VisibleForTesting
+    public boolean isKeyEnabled(int keyType) {
         //since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
         int returnValue = (mKeyAvailability >> (keyType - 1)) & 1;
         return (returnValue == 1) ? true : false;
@@ -427,15 +477,13 @@
 
     private boolean downloadKey() {
         Log.d(LOG_TAG, "starting download from: " + mURL);
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         String mcc = "";
         String mnc = "";
-        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
+        String simOperator = getSimOperator();
 
-        if (!TextUtils.isEmpty(networkOperator)) {
-            mcc = networkOperator.substring(0, 3);
-            mnc = networkOperator.substring(3);
+        if (!TextUtils.isEmpty(simOperator)) {
+            mcc = simOperator.substring(0, 3);
+            mnc = simOperator.substring(3);
             Log.d(LOG_TAG, "using values for mcc, mnc: " + mcc + "," + mnc);
         } else {
             Log.e(LOG_TAG, "mcc, mnc: is empty");
@@ -461,11 +509,35 @@
         return true;
     }
 
-    private void savePublicKey(String key, int type, String identifier, long expirationDate,
+    /**
+     * Save the public key
+     * @param certificate certificate that contains the public key.
+     * @return Pair containing the Public Key and the expiration date.
+     **/
+    @VisibleForTesting
+    public static Pair<PublicKey, Long> getKeyInformation(byte[] certificate) throws Exception {
+        InputStream inStream = new ByteArrayInputStream(certificate);
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
+        Pair<PublicKey, Long> keyInformation =
+                new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
+        return keyInformation;
+    }
+
+    /**
+     * Save the public key
+     * @param publicKey public key.
+     * @param type key-type.
+     * @param identifier which is an opaque string.
+     * @param expirationDate expiration date of the key.
+     * @param mcc
+     * @param mnc
+     **/
+    @VisibleForTesting
+    public void savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate,
                                String mcc, String mnc) {
-        byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);
         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, type, identifier,
-                keyBytes, new Date(expirationDate));
+                publicKey, new Date(expirationDate));
         mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
     }
 }
diff --git a/com/android/internal/telephony/CarrierServiceStateTracker.java b/com/android/internal/telephony/CarrierServiceStateTracker.java
index 8df201e..77a39eb 100644
--- a/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -79,13 +79,8 @@
         switch (msg.what) {
             case CARRIER_EVENT_VOICE_REGISTRATION:
             case CARRIER_EVENT_DATA_REGISTRATION:
-                handleConfigChanges();
-                break;
             case CARRIER_EVENT_VOICE_DEREGISTRATION:
             case CARRIER_EVENT_DATA_DEREGISTRATION:
-                if (isRadioOffOrAirplaneMode()) {
-                    break;
-                }
                 handleConfigChanges();
                 break;
             case NOTIFICATION_EMERGENCY_NETWORK:
@@ -317,8 +312,8 @@
             Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
                     + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
                     + "," + mSST.isRadioOn());
-            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered()
-                    || isGlobalMode()) {
+            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered() || isGlobalMode()
+                    || isRadioOffOrAirplaneMode()) {
                 return false;
             }
             return true;
diff --git a/com/android/internal/telephony/ClientWakelockTracker.java b/com/android/internal/telephony/ClientWakelockTracker.java
index 5bec60b..fa71e76 100644
--- a/com/android/internal/telephony/ClientWakelockTracker.java
+++ b/com/android/internal/telephony/ClientWakelockTracker.java
@@ -18,10 +18,10 @@
 
 import android.os.SystemClock;
 import android.telephony.ClientRequestStats;
-import android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -119,13 +119,13 @@
         return false;
     }
 
-    void dumpClientRequestTracker() {
-        Rlog.d(RIL.RILJ_LOG_TAG, "-------mClients---------------");
+    void dumpClientRequestTracker(PrintWriter pw) {
+        pw.println("-------mClients---------------");
         synchronized (mClients) {
             for (String key : mClients.keySet()) {
-                Rlog.d(RIL.RILJ_LOG_TAG, "Client : " + key);
-                Rlog.d(RIL.RILJ_LOG_TAG, mClients.get(key).toString());
+                pw.println("Client : " + key);
+                pw.println(mClients.get(key).toString());
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/com/android/internal/telephony/Connection.java b/com/android/internal/telephony/Connection.java
index 245f76c..8c54a31 100644
--- a/com/android/internal/telephony/Connection.java
+++ b/com/android/internal/telephony/Connection.java
@@ -34,6 +34,7 @@
  * {@hide}
  */
 public abstract class Connection {
+    private static final String TAG = "Connection";
 
     public interface PostDialListener {
         void onPostDialWait();
@@ -836,6 +837,16 @@
     public void setConnectionExtras(Bundle extras) {
         if (extras != null) {
             mExtras = new Bundle(extras);
+
+            int previousCount = mExtras.size();
+            // Prevent vendors from passing in extras other than primitive types and android API
+            // parcelables.
+            mExtras = mExtras.filterValues();
+            int filteredCount = mExtras.size();
+            if (filteredCount != previousCount) {
+                Rlog.i(TAG, "setConnectionExtras: filtering " + (previousCount - filteredCount)
+                        + " invalid extras.");
+            }
         } else {
             mExtras = null;
         }
diff --git a/com/android/internal/telephony/DefaultPhoneNotifier.java b/com/android/internal/telephony/DefaultPhoneNotifier.java
index c13e540..98c0a32 100644
--- a/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -125,6 +125,9 @@
         int subId = sender.getSubId();
         try {
             if (mRegistry != null) {
+                Rlog.d(LOG_TAG, "notifyCallForwardingChanged: subId=" + subId + ", isCFActive="
+                        + sender.getCallForwardingIndicator());
+
                 mRegistry.notifyCallForwardingChangedForSubscriber(subId,
                         sender.getCallForwardingIndicator());
             }
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index d95d018..ad078d6 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -286,7 +286,7 @@
             tm.setPhoneType(getPhoneId(), PhoneConstants.PHONE_TYPE_GSM);
             mIccCardProxy.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
         } else {
-            mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
+            mCdmaSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
             // This is needed to handle phone process crashes
             mIsPhoneInEcmState = getInEcmMode();
             if (mIsPhoneInEcmState) {
@@ -505,7 +505,7 @@
 
             ret = PhoneConstants.DataState.DISCONNECTED;
         } else if (mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE
-                && (isPhoneTypeCdma() ||
+                && (isPhoneTypeCdma() || isPhoneTypeCdmaLte() ||
                 (isPhoneTypeGsm() && !apnType.equals(PhoneConstants.APN_TYPE_EMERGENCY)))) {
             // If we're out of service, open TCP sockets may still work
             // but no data will flow
@@ -1063,7 +1063,7 @@
         boolean alwaysTryImsForEmergencyCarrierConfig = configManager.getConfigForSubId(getSubId())
                 .getBoolean(CarrierConfigManager.KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL);
 
-        boolean imsUseEnabled = isImsUseEnabled()
+        boolean useImsForCall = isImsUseEnabled()
                  && imsPhone != null
                  && (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
                  (imsPhone.isVideoEnabled() && VideoProfile.isVideo(videoState)))
@@ -1083,7 +1083,7 @@
         boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
 
         if (DBG) {
-            logd("imsUseEnabled=" + imsUseEnabled
+            logd("useImsForCall=" + useImsForCall
                     + ", useImsForEmergency=" + useImsForEmergency
                     + ", useImsForUt=" + useImsForUt
                     + ", isUt=" + isUt
@@ -1100,13 +1100,13 @@
 
         Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mContext);
 
-        if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
+        if ((useImsForCall && !isUt) || (isUt && useImsForUt) || useImsForEmergency) {
             try {
                 if (DBG) logd("Trying IMS PS call");
                 return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
             } catch (CallStateException e) {
                 if (DBG) logd("IMS PS call exception " + e +
-                        "imsUseEnabled =" + imsUseEnabled + ", imsPhone =" + imsPhone);
+                        "useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone);
                 // Do not throw a CallStateException and instead fall back to Circuit switch
                 // for emergency calls and MMI codes.
                 if (Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency) {
@@ -2549,6 +2549,7 @@
     private void processIccRecordEvents(int eventCode) {
         switch (eventCode) {
             case IccRecords.EVENT_CFI:
+                logi("processIccRecordEvents: EVENT_CFI");
                 notifyCallForwardingIndicator();
                 break;
         }
diff --git a/com/android/internal/telephony/InboundSmsHandler.java b/com/android/internal/telephony/InboundSmsHandler.java
index 59195f8..391de50 100644
--- a/com/android/internal/telephony/InboundSmsHandler.java
+++ b/com/android/internal/telephony/InboundSmsHandler.java
@@ -158,6 +158,17 @@
     /** New SMS received as an AsyncResult. */
     public static final int EVENT_INJECT_SMS = 8;
 
+    /** Update tracker object; used only in waiting state */
+    private static final int EVENT_UPDATE_TRACKER = 9;
+
+    /** Timeout in case state machine is stuck in a state for too long; used only in waiting
+     * state */
+    private static final int EVENT_STATE_TIMEOUT = 10;
+
+    /** Timeout duration for EVENT_STATE_TIMEOUT */
+    @VisibleForTesting
+    public static final int STATE_TIMEOUT = 30000;
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
 
@@ -450,6 +461,7 @@
                     // if any broadcasts were sent, transition to waiting state
                     InboundSmsTracker inboundSmsTracker = (InboundSmsTracker) msg.obj;
                     if (processMessagePart(inboundSmsTracker)) {
+                        sendMessage(EVENT_UPDATE_TRACKER, inboundSmsTracker);
                         transitionTo(mWaitingState);
                     } else {
                         // if event is sent from SmsBroadcastUndelivered.broadcastSms(), and
@@ -493,18 +505,41 @@
      * {@link IdleState} after any deferred {@link #EVENT_BROADCAST_SMS} messages are handled.
      */
     private class WaitingState extends State {
+        private InboundSmsTracker mTracker;
+
+        @Override
+        public void enter() {
+            if (DBG) log("entering Waiting state");
+            mTracker = null;
+            sendMessageDelayed(EVENT_STATE_TIMEOUT, STATE_TIMEOUT);
+        }
+
         @Override
         public void exit() {
             if (DBG) log("exiting Waiting state");
             // Before moving to idle state, set wakelock timeout to WAKE_LOCK_TIMEOUT milliseconds
             // to give any receivers time to take their own wake locks
             setWakeLockTimeout(WAKELOCK_TIMEOUT);
+            if (VDBG) {
+                if (hasMessages(EVENT_STATE_TIMEOUT)) {
+                    log("exiting Waiting state: removing EVENT_STATE_TIMEOUT from message queue");
+                }
+                if (hasMessages(EVENT_UPDATE_TRACKER)) {
+                    log("exiting Waiting state: removing EVENT_UPDATE_TRACKER from message queue");
+                }
+            }
+            removeMessages(EVENT_STATE_TIMEOUT);
+            removeMessages(EVENT_UPDATE_TRACKER);
         }
 
         @Override
         public boolean processMessage(Message msg) {
             log("WaitingState.processMessage:" + msg.what);
             switch (msg.what) {
+                case EVENT_UPDATE_TRACKER:
+                    mTracker = (InboundSmsTracker) msg.obj;
+                    return HANDLED;
+
                 case EVENT_BROADCAST_SMS:
                     // defer until the current broadcast completes
                     deferMessage(msg);
@@ -520,6 +555,18 @@
                     // not ready to return to idle; ignore
                     return HANDLED;
 
+                case EVENT_STATE_TIMEOUT:
+                    // stuck in WaitingState for too long; drop the message and exit this state
+                    if (mTracker != null) {
+                        log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; dropping message");
+                        dropSms(new SmsBroadcastReceiver(mTracker));
+                    } else {
+                        log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; mTracker is null "
+                                + "- sending EVENT_BROADCAST_COMPLETE");
+                        sendMessage(EVENT_BROADCAST_COMPLETE);
+                    }
+                    return HANDLED;
+
                 default:
                     // parent state handles the other message types
                     return NOT_HANDLED;
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index 28e4556..6acc874 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -56,6 +56,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.VoLteServiceState;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsConfig;
@@ -226,6 +227,9 @@
     // Key used to read/write "disable DNS server check" pref (used for testing)
     private static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key";
 
+    // Integer used to let the calling application know that the we are ignoring auto mode switch.
+    private static final int ALREADY_IN_AUTO_SELECTION = 1;
+
     /**
      * This method is invoked when the Phone exits Emergency Callback Mode.
      */
@@ -1205,6 +1209,11 @@
             mCi.setNetworkSelectionModeAutomatic(msg);
         } else {
             Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - already auto, ignoring");
+            // let the calling application know that the we are ignoring automatic mode switch.
+            if (nsm.message != null) {
+                nsm.message.arg1 = ALREADY_IN_AUTO_SELECTION;
+            }
+
             ar.userObj = nsm;
             handleSetSelectNetwork(ar);
         }
@@ -1789,7 +1798,7 @@
         int status = enable ? IccRecords.CALL_FORWARDING_STATUS_ENABLED :
                 IccRecords.CALL_FORWARDING_STATUS_DISABLED;
         int subId = getSubId();
-        Rlog.d(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
+        Rlog.i(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
                 " in pref " + CF_STATUS + subId);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -1831,6 +1840,9 @@
         if (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_UNKNOWN) {
             callForwardingIndicator = getCallForwardingIndicatorFromSharedPref();
         }
+        Rlog.v(LOG_TAG, "getCallForwardingIndicator: iccForwardingFlag=" + (r != null
+                    ? r.getVoiceCallForwardingFlag() : "null") + ", sharedPrefFlag="
+                    + getCallForwardingIndicatorFromSharedPref());
         return (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_ENABLED);
     }
 
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 8bb2125..84c2b65 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -4774,7 +4774,7 @@
         }
         pw.println(" mLastNITZTimeInfo=" + Arrays.toString(mLastNITZTimeInfo));
         pw.println(" mTestingEmergencyCall=" + mTestingEmergencyCall.get());
-        mClientWakelockTracker.dumpClientRequestTracker();
+        mClientWakelockTracker.dumpClientRequestTracker(pw);
     }
 
     public List<ClientRequestStats> getClientRequestStats() {
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index b379440..c34cbb2 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -210,6 +210,7 @@
     protected static final int EVENT_ALL_DATA_DISCONNECTED             = 49;
     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 String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -354,6 +355,14 @@
                 }
                 // update voicemail count and notify message waiting changed
                 mPhone.updateVoiceMail();
+
+                // cancel notifications if we see SIM_NOT_INSERTED (This happens on bootup before
+                // the SIM is first detected and then subsequently on SIM removals)
+                if (mSubscriptionController.getSlotIndex(subId)
+                        == SubscriptionManager.SIM_NOT_INSERTED) {
+                    // this is handled on the main thread to mitigate racing with setNotification().
+                    sendMessage(obtainMessage(EVENT_SIM_NOT_INSERTED));
+                }
             }
         }
     };
@@ -446,12 +455,15 @@
     public static final int CS_NORMAL_ENABLED = 1005;     // Access Control blocks normal voice/sms service
     public static final int CS_EMERGENCY_ENABLED = 1006;  // Access Control blocks emergency call service
     public static final int CS_REJECT_CAUSE_ENABLED = 2001;     // Notify MM rejection cause
-    public static final int CS_REJECT_CAUSE_DISABLED = 2002;    // Cancel MM rejection cause
     /** Notification id. */
     public static final int PS_NOTIFICATION = 888;  // Id to update and cancel PS restricted
     public static final int CS_NOTIFICATION = 999;  // Id to update and cancel CS restricted
     public static final int CS_REJECT_CAUSE_NOTIFICATION = 111; // Id to update and cancel MM
                                                                 // rejection cause
+
+    /** To identify whether EVENT_SIM_READY is received or not */
+    private boolean mIsSimReady = false;
+
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1064,6 +1076,11 @@
 
             case EVENT_ICC_CHANGED:
                 onUpdateIccAvailability();
+                if (mUiccApplcation != null
+                        && mUiccApplcation.getState() != AppState.APPSTATE_READY) {
+                    mIsSimReady = false;
+                    updateSpnDisplay();
+                }
                 break;
 
             case EVENT_GET_CELL_INFO_LIST: {
@@ -1121,6 +1138,7 @@
                 // Reset the mPreviousSubId so we treat a SIM power bounce
                 // as a first boot.  See b/19194287
                 mOnSubscriptionsChangedListener.mPreviousSubId.set(-1);
+                mIsSimReady = true;
                 pollState();
                 // Signal strength polling stops when radio is off
                 queueNextSignalStrengthPoll();
@@ -1298,6 +1316,14 @@
                 }
                 break;
 
+            case EVENT_SIM_NOT_INSERTED:
+                if (DBG) log("EVENT_SIM_NOT_INSERTED");
+                cancelAllNotifications();
+                mMdn = null;
+                mMin = null;
+                mIsMinInfoReady = false;
+                break;
+
             case EVENT_ALL_DATA_DISCONNECTED:
                 int dds = SubscriptionManager.getDefaultDataSubscriptionId();
                 ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
@@ -2222,7 +2248,12 @@
             if (combinedRegState == ServiceState.STATE_OUT_OF_SERVICE
                     || combinedRegState == ServiceState.STATE_EMERGENCY_ONLY) {
                 showPlmn = true;
-                if (mEmergencyOnly) {
+
+                // Force display no service
+                final boolean forceDisplayNoService = mPhone.getContext().getResources().getBoolean(
+                        com.android.internal.R.bool.config_display_no_service_when_sim_unready)
+                                && !mIsSimReady;
+                if (mEmergencyOnly && !forceDisplayNoService) {
                     // No service but emergency call allowed
                     plmn = Resources.getSystem().
                             getText(com.android.internal.R.string.emergency_calls_only).toString();
@@ -2825,7 +2856,7 @@
         }
 
         if (hasRejectCauseChanged) {
-            setNotification(mRejectCode == 0 ? CS_REJECT_CAUSE_DISABLED : CS_REJECT_CAUSE_ENABLED);
+            setNotification(CS_REJECT_CAUSE_ENABLED);
         }
 
         if (hasChanged) {
@@ -3833,6 +3864,18 @@
     }
 
     /**
+     * Cancels all notifications posted to NotificationManager. These notifications for restricted
+     * state and rejection cause for cs registration are no longer valid after the SIM has been
+     * removed.
+     */
+    private void cancelAllNotifications() {
+        if (DBG) log("setNotification: cancelAllNotifications");
+        NotificationManager notificationManager = (NotificationManager)
+                mPhone.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancelAll();
+    }
+
+    /**
      * Post a notification to NotificationManager for restricted state and
      * rejection cause for cs registration
      *
@@ -3907,17 +3950,14 @@
                 notificationId = CS_REJECT_CAUSE_NOTIFICATION;
                 int resId = selectResourceForRejectCode(mRejectCode);
                 if (0 == resId) {
-                    // cancel notification because current reject code is not handled.
-                    notifyType = CS_REJECT_CAUSE_DISABLED;
+                    loge("setNotification: mRejectCode=" + mRejectCode + " is not handled.");
+                    return;
                 } else {
                     icon = com.android.internal.R.drawable.stat_notify_mmcc_indication_icn;
                     title = Resources.getSystem().getString(resId);
                     details = null;
                 }
                 break;
-            case CS_REJECT_CAUSE_DISABLED:
-                notificationId = CS_REJECT_CAUSE_NOTIFICATION;
-                break;
         }
 
         if (DBG) {
@@ -3941,8 +3981,7 @@
         NotificationManager notificationManager = (NotificationManager)
                 context.getSystemService(Context.NOTIFICATION_SERVICE);
 
-        if (notifyType == PS_DISABLED || notifyType == CS_DISABLED
-                || notifyType == CS_REJECT_CAUSE_DISABLED) {
+        if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) {
             // cancel previous post notification
             notificationManager.cancel(notificationId);
         } else {
diff --git a/com/android/internal/telephony/cat/AppInterface.java b/com/android/internal/telephony/cat/AppInterface.java
index c78b7f8..1f2d3a0 100644
--- a/com/android/internal/telephony/cat/AppInterface.java
+++ b/com/android/internal/telephony/cat/AppInterface.java
@@ -84,6 +84,7 @@
         SET_UP_MENU(0x25),
         SET_UP_CALL(0x10),
         PROVIDE_LOCAL_INFORMATION(0x26),
+        LANGUAGE_NOTIFICATION(0x35),
         OPEN_CHANNEL(0x40),
         CLOSE_CHANNEL(0x41),
         RECEIVE_DATA(0x42),
diff --git a/com/android/internal/telephony/cat/CatService.java b/com/android/internal/telephony/cat/CatService.java
index a242de4..cd7a756 100644
--- a/com/android/internal/telephony/cat/CatService.java
+++ b/com/android/internal/telephony/cat/CatService.java
@@ -16,15 +16,21 @@
 
 package com.android.internal.telephony.cat;
 
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.backup.BackupManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources.NotFoundException;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.LocaleList;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -450,6 +456,18 @@
                     ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString();
                 }
                 break;
+            case LANGUAGE_NOTIFICATION:
+                String language = ((LanguageParams) cmdParams).mLanguage;
+                ResultCode result = ResultCode.OK;
+                if (language != null && language.length() > 0) {
+                    try {
+                        changeLanguage(language);
+                    } catch (RemoteException e) {
+                        result = ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS;
+                    }
+                }
+                sendTerminalResponse(cmdParams.mCmdDet, result, false, 0, null);
+                return;
             case OPEN_CHANNEL:
             case CLOSE_CHANNEL:
             case RECEIVE_DATA:
@@ -881,8 +899,9 @@
         // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true).
         intent.putExtra(AppInterface.CARD_STATUS, cardPresent);
         intent.setComponent(AppInterface.getDefaultSTKApplication());
+        intent.putExtra("SLOT_ID", mSlotId);
         CatLog.d(this, "Sending Card Status: "
-                + cardState + " " + "cardPresent: " + cardPresent);
+                + cardState + " " + "cardPresent: " + cardPresent +  "SLOT_ID: " +  mSlotId);
         mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
     }
 
@@ -1006,6 +1025,13 @@
                 }
                 break;
             case LAUNCH_BROWSER:
+                if (resMsg.mResCode == ResultCode.LAUNCH_BROWSER_ERROR) {
+                    // Additional info for Default URL unavailable.
+                    resMsg.setAdditionalInfo(0x04);
+                } else {
+                    resMsg.mIncludeAdditionalInfo = false;
+                    resMsg.mAdditionalInfo = 0;
+                }
                 break;
             // 3GPP TS.102.223: Open Channel alpha confirmation should not send TR
             case OPEN_CHANNEL:
@@ -1121,4 +1147,13 @@
             mCmdIf.reportStkServiceIsRunning(null);
         }
     }
+
+    private void changeLanguage(String language) throws RemoteException {
+        IActivityManager am = ActivityManagerNative.getDefault();
+        Configuration config = am.getConfiguration();
+        config.setLocales(new LocaleList(new Locale(language), LocaleList.getDefault()));
+        config.userSetLocale = true;
+        am.updatePersistentConfiguration(config);
+        BackupManager.dataChanged("com.android.providers.settings");
+    }
 }
diff --git a/com/android/internal/telephony/cat/CommandParams.java b/com/android/internal/telephony/cat/CommandParams.java
index 7dfedab..59cd414 100644
--- a/com/android/internal/telephony/cat/CommandParams.java
+++ b/com/android/internal/telephony/cat/CommandParams.java
@@ -150,6 +150,15 @@
     }
 }
 
+class LanguageParams extends CommandParams {
+    String mLanguage;
+
+    LanguageParams(CommandDetails cmdDet, String lang) {
+        super(cmdDet);
+        mLanguage = lang;
+    }
+}
+
 class SelectItemParams extends CommandParams {
     Menu mMenu = null;
     boolean mLoadTitleIcon = false;
diff --git a/com/android/internal/telephony/cat/CommandParamsFactory.java b/com/android/internal/telephony/cat/CommandParamsFactory.java
index 3dd5337..eb92888 100644
--- a/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -19,12 +19,15 @@
 import android.graphics.Bitmap;
 import android.os.Handler;
 import android.os.Message;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.uicc.IccFileHandler;
 
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
+
 import static com.android.internal.telephony.cat.CatCmdMessage.
                    SetupEventListConstants.USER_ACTIVITY_EVENT;
 import static com.android.internal.telephony.cat.CatCmdMessage.
@@ -47,6 +50,8 @@
     private int mIconLoadState = LOAD_NO_ICON;
     private RilMessageDecoder mCaller = null;
     private boolean mloadIcon = false;
+    private String mSavedLanguage;
+    private String mRequestedLanguage;
 
     // constants
     static final int MSG_ID_LOAD_ICON_DONE = 1;
@@ -66,6 +71,10 @@
     static final int DTTZ_SETTING                           = 0x03;
     static final int LANGUAGE_SETTING                       = 0x04;
 
+    // Command Qualifier value for language notification command
+    static final int NON_SPECIFIC_LANGUAGE                  = 0x00;
+    static final int SPECIFIC_LANGUAGE                      = 0x01;
+
     // As per TS 102.223 Annex C, Structure of CAT communications,
     // the APDU length can be max 255 bytes. This leaves only 239 bytes for user
     // input string. CMD details TLV + Device IDs TLV + Result TLV + Other
@@ -203,6 +212,9 @@
              case PROVIDE_LOCAL_INFORMATION:
                 cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
                 break;
+             case LANGUAGE_NOTIFICATION:
+                 cmdPending = processLanguageNotification(cmdDet, ctlvs);
+                 break;
              case OPEN_CHANNEL:
              case CLOSE_CHANNEL:
              case RECEIVE_DATA:
@@ -1014,6 +1026,67 @@
         return false;
     }
 
+    /**
+     * Processes LANGUAGE_NOTIFICATION proactive command from the SIM card.
+     *
+     * The SPECIFIC_LANGUAGE notification sets the specified language.
+     * The NON_SPECIFIC_LANGUAGE notification restores the last specifically set language.
+     *
+     * @param cmdDet Command Details object retrieved from the proactive command object
+     * @param ctlvs List of ComprehensionTlv objects following Command Details
+     *        object and Device Identities object within the proactive command
+     * @return false. This function always returns false meaning that the command
+     *         processing is  not pending and additional asynchronous processing
+     *         is not required.
+     */
+    private boolean processLanguageNotification(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
+            throws ResultException {
+        CatLog.d(this, "process Language Notification");
+
+        String desiredLanguage = null;
+        String currentLanguage = Locale.getDefault().getLanguage();
+        switch (cmdDet.commandQualifier) {
+            case NON_SPECIFIC_LANGUAGE:
+                if (!TextUtils.isEmpty(mSavedLanguage) && (!TextUtils.isEmpty(mRequestedLanguage)
+                        && mRequestedLanguage.equals(currentLanguage))) {
+                    CatLog.d(this, "Non-specific language notification changes the language "
+                            + "setting back to " + mSavedLanguage);
+                    desiredLanguage = mSavedLanguage;
+                }
+
+                mSavedLanguage = null;
+                mRequestedLanguage = null;
+                break;
+            case SPECIFIC_LANGUAGE:
+                ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.LANGUAGE, ctlvs);
+                if (ctlv != null) {
+                    int valueLen = ctlv.getLength();
+                    if (valueLen != 2) {
+                        throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+                    }
+
+                    byte[] rawValue = ctlv.getRawValue();
+                    int valueIndex = ctlv.getValueIndex();
+                    desiredLanguage = GsmAlphabet.gsm8BitUnpackedToString(rawValue, valueIndex, 2);
+
+                    if (TextUtils.isEmpty(mSavedLanguage) || (!TextUtils.isEmpty(mRequestedLanguage)
+                            && !mRequestedLanguage.equals(currentLanguage))) {
+                        mSavedLanguage = currentLanguage;
+                    }
+                    mRequestedLanguage = desiredLanguage;
+                    CatLog.d(this, "Specific language notification changes the language setting to "
+                            + mRequestedLanguage);
+                }
+                break;
+            default:
+                CatLog.d(this, "LN[" + cmdDet.commandQualifier + "] Command Not Supported");
+                break;
+        }
+
+        mCmdParams = new LanguageParams(cmdDet, desiredLanguage);
+        return false;
+    }
+
     private boolean processBIPClient(CommandDetails cmdDet,
                                      List<ComprehensionTlv> ctlvs) throws ResultException {
         AppInterface.CommandType commandType =
diff --git a/com/android/internal/telephony/cdma/SmsMessage.java b/com/android/internal/telephony/cdma/SmsMessage.java
index 629173d..7a53ef6 100644
--- a/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/com/android/internal/telephony/cdma/SmsMessage.java
@@ -161,7 +161,7 @@
 
             // Second byte is the MSG_LEN, length of the message
             // See 3GPP2 C.S0023 3.4.27
-            int size = data[1];
+            int size = data[1] & 0xFF;
 
             // Note: Data may include trailing FF's.  That's OK; message
             // should still parse correctly.
diff --git a/com/android/internal/telephony/dataconnection/DcTracker.java b/com/android/internal/telephony/dataconnection/DcTracker.java
index f9b0017..fb756cd 100644
--- a/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -149,8 +149,6 @@
     private static final boolean DATA_STALL_SUSPECTED = true;
     private static final boolean DATA_STALL_NOT_SUSPECTED = false;
 
-    private String RADIO_RESET_PROPERTY = "gsm.radioreset";
-
     private static final String INTENT_RECONNECT_ALARM =
             "com.android.internal.telephony.data-reconnect";
     private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
@@ -2246,7 +2244,7 @@
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
 
         // Get current sub id.
-        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        int subId = mPhone.getSubId();
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
 
         if (DBG) {
@@ -4546,13 +4544,11 @@
         public static final int CLEANUP                 = 1;
         public static final int REREGISTER              = 2;
         public static final int RADIO_RESTART           = 3;
-        public static final int RADIO_RESTART_WITH_PROP = 4;
 
         private static boolean isAggressiveRecovery(int value) {
             return ((value == RecoveryAction.CLEANUP) ||
                     (value == RecoveryAction.REREGISTER) ||
-                    (value == RecoveryAction.RADIO_RESTART) ||
-                    (value == RecoveryAction.RADIO_RESTART_WITH_PROP));
+                    (value == RecoveryAction.RADIO_RESTART));
         }
     }
 
@@ -4598,23 +4594,6 @@
                 EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
                         mSentSinceLastRecv);
                 if (DBG) log("restarting radio");
-                putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP);
-                restartRadio();
-                break;
-            case RecoveryAction.RADIO_RESTART_WITH_PROP:
-                // This is in case radio restart has not recovered the data.
-                // It will set an additional "gsm.radioreset" property to tell
-                // RIL or system to take further action.
-                // The implementation of hard reset recovery action is up to OEM product.
-                // Once RADIO_RESET property is consumed, it is expected to set back
-                // to false by RIL.
-                EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP, -1);
-                if (DBG) log("restarting radio with gsm.radioreset to true");
-                SystemProperties.set(RADIO_RESET_PROPERTY, "true");
-                // give 1 sec so property change can be notified.
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {}
                 restartRadio();
                 putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
                 break;
diff --git a/com/android/internal/telephony/gsm/GsmSmsAddress.java b/com/android/internal/telephony/gsm/GsmSmsAddress.java
index 2fbf7ed..bd8c83e 100644
--- a/com/android/internal/telephony/gsm/GsmSmsAddress.java
+++ b/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.gsm;
 
 import android.telephony.PhoneNumberUtils;
+
 import java.text.ParseException;
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.SmsAddress;
@@ -71,8 +72,11 @@
                 // Make sure the final unused BCD digit is 0xf
                 origBytes[length - 1] |= 0xf0;
             }
-            address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
-                    OFFSET_TOA, length - OFFSET_TOA);
+            address = PhoneNumberUtils.calledPartyBCDToString(
+                    origBytes,
+                    OFFSET_TOA,
+                    length - OFFSET_TOA,
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
 
             // And restore origBytes
             origBytes[length - 1] = lastByte;
diff --git a/com/android/internal/telephony/gsm/SmsMessage.java b/com/android/internal/telephony/gsm/SmsMessage.java
index d4098d9..1ca19e0 100644
--- a/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/com/android/internal/telephony/gsm/SmsMessage.java
@@ -535,8 +535,8 @@
             } else {
                 // SC address
                 try {
-                    ret = PhoneNumberUtils
-                            .calledPartyBCDToString(mPdu, mCur, len);
+                    ret = PhoneNumberUtils.calledPartyBCDToString(
+                            mPdu, mCur, len, PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
                 } catch (RuntimeException tr) {
                     Rlog.d(LOG_TAG, "invalid SC address: ", tr);
                     ret = null;
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index 45dc0b2..03d83df 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -1038,6 +1038,9 @@
                 break;
             case ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE:
                 error = CommandException.Error.RADIO_NOT_AVAILABLE;
+                break;
+            case ImsReasonInfo.CODE_FDN_BLOCKED:
+                error = CommandException.Error.FDN_CHECK_FAILURE;
             default:
                 break;
         }
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index ab30878..f837b56 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -2566,7 +2566,7 @@
                                 && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
                                 && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
                 if (isHandoverFromWifi && imsCall.isVideoCall()) {
-                    if (mNotifyHandoverVideoFromWifiToLTE) {
+                    if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
                         log("onCallHandover :: notifying of WIFI to LTE handover.");
                         conn.onConnectionEvent(
                                 TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
@@ -2575,7 +2575,7 @@
                     if (!mIsDataEnabled && mIsViLteDataMetered) {
                         // Call was downgraded from WIFI to LTE and data is metered; downgrade the
                         // call now.
-                        downgradeVideoCall(ImsReasonInfo.CODE_DATA_DISABLED, conn);
+                        downgradeVideoCall(ImsReasonInfo.CODE_WIFI_LOST, conn);
                     }
                 }
             } else {
@@ -3535,8 +3535,9 @@
                 // If the carrier supports downgrading to voice, then we can simply issue a
                 // downgrade to voice instead of terminating the call.
                 modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
-            } else if (mSupportPauseVideo) {
-                // The carrier supports video pause signalling, so pause the video.
+            } else if (mSupportPauseVideo && reasonCode != ImsReasonInfo.CODE_WIFI_LOST) {
+                // The carrier supports video pause signalling, so pause the video if we didn't just
+                // lose wifi; in that case just disconnect.
                 mShouldUpdateImsConfigOnDisconnect = true;
                 conn.pauseVideo(VideoPauseTracker.SOURCE_DATA_ENABLED);
             } else {
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 4e3957e..9c99055 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -40,6 +40,7 @@
 import android.text.TextUtils;
 
 import com.android.ims.ImsException;
+import com.android.ims.ImsReasonInfo;
 import com.android.ims.ImsSsInfo;
 import com.android.ims.ImsUtInterface;
 import com.android.internal.telephony.CallForwardInfo;
@@ -1172,6 +1173,14 @@
     }
 
     private CharSequence getErrorMessage(AsyncResult ar) {
+        if (ar.exception instanceof CommandException) {
+            CommandException.Error err = ((CommandException) (ar.exception)).getCommandError();
+            if (err == CommandException.Error.FDN_CHECK_FAILURE) {
+                Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
+                return mContext.getText(com.android.internal.R.string.mmiFdnError);
+            }
+        }
+
         return mContext.getText(com.android.internal.R.string.mmiError);
     }
 
@@ -1216,18 +1225,15 @@
                 if (err.getCommandError() == CommandException.Error.PASSWORD_INCORRECT) {
                     sb.append(mContext.getText(
                             com.android.internal.R.string.passwordIncorrect));
+                } else if (err.getCommandError() == CommandException.Error.FDN_CHECK_FAILURE) {
+                    sb.append(mContext.getText(com.android.internal.R.string.mmiFdnError));
                 } else if (err.getMessage() != null) {
                     sb.append(err.getMessage());
                 } else {
                     sb.append(mContext.getText(com.android.internal.R.string.mmiError));
                 }
-            } else {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+            } else if (ar.exception instanceof ImsException) {
+                sb.append(getImsErrorMessage(ar));
             }
         } else if (isActivate()) {
             mState = State.COMPLETE;
@@ -1360,12 +1366,7 @@
             mState = State.FAILED;
 
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             }
             else {
                 sb.append(getErrorMessage(ar));
@@ -1421,21 +1422,14 @@
         StringBuilder sb = new StringBuilder(getScString());
         sb.append("\n");
 
+        mState = State.FAILED;
         if (ar.exception != null) {
-            mState = State.FAILED;
-
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             } else {
                 sb.append(getErrorMessage(ar));
             }
         } else {
-            mState = State.FAILED;
             ImsSsInfo ssInfo = null;
             if (ar.result instanceof Bundle) {
                 Rlog.d(LOG_TAG, "onSuppSvcQueryComplete: Received CLIP/COLP/COLR Response.");
@@ -1486,12 +1480,7 @@
             mState = State.FAILED;
 
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             } else {
                 sb.append(getErrorMessage(ar));
             }
@@ -1525,14 +1514,8 @@
         mState = State.FAILED;
 
         if (ar.exception != null) {
-
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             }
         } else {
             Bundle ssInfo = (Bundle) ar.result;
@@ -1623,12 +1606,7 @@
             mState = State.FAILED;
 
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             } else {
                 sb.append(getErrorMessage(ar));
             }
@@ -1676,6 +1654,17 @@
         return sb;
     }
 
+    private CharSequence getImsErrorMessage(AsyncResult ar) {
+        ImsException error = (ImsException) ar.exception;
+        if (error.getCode() == ImsReasonInfo.CODE_FDN_BLOCKED) {
+            return mContext.getText(com.android.internal.R.string.mmiFdnError);
+        } else if (error.getMessage() != null) {
+            return error.getMessage();
+        } else {
+            return getErrorMessage(ar);
+        }
+    }
+
     @Override
     public ResultReceiver getUssdCallbackReceiver() {
         return this.mCallbackReceiver;
diff --git a/com/android/internal/telephony/uicc/AdnRecord.java b/com/android/internal/telephony/uicc/AdnRecord.java
index 203236c..4414caf 100644
--- a/com/android/internal/telephony/uicc/AdnRecord.java
+++ b/com/android/internal/telephony/uicc/AdnRecord.java
@@ -19,8 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
 
@@ -248,7 +248,8 @@
             Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
             return null;
         } else {
-            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(mNumber);
+            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                    mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
             System.arraycopy(bcdNumber, 0, adnString,
                     footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
@@ -289,7 +290,10 @@
             }
 
             mNumber += PhoneNumberUtils.calledPartyBCDFragmentToString(
-                                        extRecord, 2, 0xff & extRecord[1]);
+                    extRecord,
+                    2,
+                    0xff & extRecord[1],
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
             // We don't support ext record chaining.
 
@@ -327,7 +331,10 @@
             // the ME (see note 2)."
 
             mNumber = PhoneNumberUtils.calledPartyBCDToString(
-                            record, footerOffset + 1, numberLength);
+                    record,
+                    footerOffset + 1,
+                    numberLength,
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
 
             mExtRecord = 0xff & record[record.length - 1];
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 724b478..dad1ee2 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -563,7 +563,8 @@
                 // Spec reference for EF_CFIS contents, TS 51.011 section 10.3.46.
                 if (enable && !TextUtils.isEmpty(dialNumber)) {
                     logv("EF_CFIS: updating cf number, " + Rlog.pii(LOG_TAG, dialNumber));
-                    byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(dialNumber);
+                    byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                            dialNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
                     System.arraycopy(bcdNumber, 0, mEfCfis, CFIS_TON_NPI_OFFSET, bcdNumber.length);
 
diff --git a/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index e50f40c..bfa458b 100644
--- a/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -54,7 +54,10 @@
     private static final String LOG_TAG = "UiccCarrierPrivilegeRules";
     private static final boolean DBG = false;
 
-    private static final String AID = "A00000015141434C00";
+    private static final String ARAM_AID = "A00000015141434C00";
+    private static final String ARAD_AID = "A00000015144414300";
+    private static final int ARAM = 1;
+    private static final int ARAD = 0;
     private static final int CLA = 0x80;
     private static final int COMMAND = 0xCA;
     private static final int P1 = 0xFF;
@@ -184,18 +187,21 @@
     private String mStatusMessage;  // Only used for debugging.
     private int mChannelId; // Channel Id for communicating with UICC.
     private int mRetryCount;  // Number of retries for open logical channel.
+    private boolean mCheckedRules = false;  // Flag that used to mark whether get rules from ARA-D.
+    private int mAIDInUse;  // Message component to identify which AID is currently in-use.
     private final Runnable mRetryRunnable = new Runnable() {
         @Override
         public void run() {
-            openChannel();
+            openChannel(mAIDInUse);
         }
     };
 
-    private void openChannel() {
+    private void openChannel(int aidId) {
         // Send open logical channel request.
+        String aid = (aidId == ARAD) ? ARAD_AID : ARAM_AID;
         int p2 = 0x00;
-        mUiccCard.iccOpenLogicalChannel(AID, p2, /* supported p2 value */
-            obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, null));
+        mUiccCard.iccOpenLogicalChannel(aid, p2, /* supported p2 value */
+                obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, 0, aidId, null));
     }
 
     public UiccCarrierPrivilegeRules(UiccCard uiccCard, Message loadedCallback) {
@@ -207,7 +213,9 @@
         mRules = "";
         mAccessRules = new ArrayList<>();
 
-        openChannel();
+        // Open logical channel with ARA_D.
+        mAIDInUse = ARAD;
+        openChannel(mAIDInUse);
     }
 
     /**
@@ -391,6 +399,7 @@
     @Override
     public void handleMessage(Message msg) {
         AsyncResult ar;
+        mAIDInUse = msg.arg2;  // 0 means ARA-D and 1 means ARA-M.
 
         switch (msg.what) {
 
@@ -400,23 +409,34 @@
                 if (ar.exception == null && ar.result != null) {
                     mChannelId = ((int[]) ar.result)[0];
                     mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3,
-                            DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
-                                    mChannelId));
+                            DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId,
+                                    mAIDInUse));
                 } else {
                     // MISSING_RESOURCE could be due to logical channels temporarily unavailable,
                     // so we retry up to MAX_RETRY times, with an interval of RETRY_INTERVAL_MS.
                     if (ar.exception instanceof CommandException && mRetryCount < MAX_RETRY
                             && ((CommandException) (ar.exception)).getCommandError()
-                                    == CommandException.Error.MISSING_RESOURCE) {
+                            == CommandException.Error.MISSING_RESOURCE) {
                         mRetryCount++;
                         removeCallbacks(mRetryRunnable);
                         postDelayed(mRetryRunnable, RETRY_INTERVAL_MS);
                     } else {
-                        // if rules cannot be read from ARA applet,
-                        // fallback to PKCS15-based ARF.
-                        log("No ARA, try ARF next.");
-                        mUiccPkcs15 = new UiccPkcs15(mUiccCard,
-                                obtainMessage(EVENT_PKCS15_READ_DONE));
+                        if (mAIDInUse == ARAD) {
+                            // Open logical channel with ARA_M.
+                            mRules = "";
+                            openChannel(1);
+                        }
+                        if (mAIDInUse == ARAM) {
+                            if (mCheckedRules) {
+                                updateState(STATE_LOADED, "Success!");
+                            } else {
+                                // if rules cannot be read from both ARA_D and ARA_M applet,
+                                // fallback to PKCS15-based ARF.
+                                log("No ARA, try ARF next.");
+                                mUiccPkcs15 = new UiccPkcs15(mUiccCard,
+                                        obtainMessage(EVENT_PKCS15_READ_DONE));
+                            }
+                        }
                     }
                 }
                 break;
@@ -432,34 +452,49 @@
                             mRules += IccUtils.bytesToHexString(response.payload)
                                     .toUpperCase(Locale.US);
                             if (isDataComplete()) {
-                                mAccessRules = parseRules(mRules);
-                                updateState(STATE_LOADED, "Success!");
+                                mAccessRules.addAll(parseRules(mRules));
+                                if (mAIDInUse == ARAD) {
+                                    mCheckedRules = true;
+                                } else {
+                                    updateState(STATE_LOADED, "Success!");
+                                }
                             } else {
                                 mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND,
                                         P1, P2_EXTENDED_DATA, P3, DATA,
                                         obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
-                                                mChannelId));
+                                                mChannelId, mAIDInUse));
                                 break;
                             }
                         } catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
-                            updateState(STATE_ERROR, "Error parsing rules: " + ex);
+                            if (mAIDInUse == ARAM) {
+                                updateState(STATE_ERROR, "Error parsing rules: " + ex);
+                            }
                         }
                     } else {
-                        String errorMsg = "Invalid response: payload=" + response.payload
-                                + " sw1=" + response.sw1 + " sw2=" + response.sw2;
-                        updateState(STATE_ERROR, errorMsg);
+                        if (mAIDInUse == ARAM) {
+                            String errorMsg = "Invalid response: payload=" + response.payload
+                                    + " sw1=" + response.sw1 + " sw2=" + response.sw2;
+                            updateState(STATE_ERROR, errorMsg);
+                        }
                     }
                 } else {
-                    updateState(STATE_ERROR, "Error reading value from SIM.");
+                    if (mAIDInUse == ARAM) {
+                        updateState(STATE_ERROR, "Error reading value from SIM.");
+                    }
                 }
 
                 mUiccCard.iccCloseLogicalChannel(mChannelId, obtainMessage(
-                        EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
+                        EVENT_CLOSE_LOGICAL_CHANNEL_DONE, 0, mAIDInUse));
                 mChannelId = -1;
                 break;
 
             case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
                 log("EVENT_CLOSE_LOGICAL_CHANNEL_DONE");
+                if (mAIDInUse == ARAD) {
+                    // Close logical channel with ARA_D and then open logical channel with ARA_M.
+                    mRules = "";
+                    openChannel(1);
+                }
                 break;
 
             case EVENT_PKCS15_READ_DONE:
@@ -492,7 +527,7 @@
             String lengthBytes = allRules.parseLength(mRules);
             log("isDataComplete lengthBytes: " + lengthBytes);
             if (mRules.length() == TAG_ALL_REF_AR_DO.length() + lengthBytes.length() +
-                                   allRules.length) {
+                    allRules.length) {
                 log("isDataComplete yes");
                 return true;
             } else {
@@ -522,7 +557,7 @@
             if (accessRule != null) {
                 accessRules.add(accessRule);
             } else {
-              Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
+                Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
             }
         }
         return accessRules;
@@ -644,15 +679,15 @@
      * Converts state into human readable format.
      */
     private String getStateString(int state) {
-      switch (state) {
-        case STATE_LOADING:
-            return "STATE_LOADING";
-        case STATE_LOADED:
-            return "STATE_LOADED";
-        case STATE_ERROR:
-            return "STATE_ERROR";
-        default:
-            return "UNKNOWN";
-      }
+        switch (state) {
+            case STATE_LOADING:
+                return "STATE_LOADING";
+            case STATE_LOADED:
+                return "STATE_LOADED";
+            case STATE_ERROR:
+                return "STATE_ERROR";
+            default:
+                return "UNKNOWN";
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/com/android/internal/util/BitUtils.java b/com/android/internal/util/BitUtils.java
index 28f12eb..ba80aea 100644
--- a/com/android/internal/util/BitUtils.java
+++ b/com/android/internal/util/BitUtils.java
@@ -93,6 +93,10 @@
         return s & 0xffff;
     }
 
+    public static int uint16(byte hi, byte lo) {
+        return ((hi & 0xff) << 8) | (lo & 0xff);
+    }
+
     public static long uint32(int i) {
         return i & 0xffffffffL;
     }
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
new file mode 100644
index 0000000..ad84353
--- /dev/null
+++ b/com/android/internal/util/RingBuffer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.util;
+
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * A simple ring buffer structure with bounded capacity backed by an array.
+ * Events can always be added at the logical end of the buffer. If the buffer is
+ * full, oldest events are dropped when new events are added.
+ * {@hide}
+ */
+public class RingBuffer<T> {
+
+    // Array for storing events.
+    private final T[] mBuffer;
+    // Cursor keeping track of the logical end of the array. This cursor never
+    // wraps and instead keeps track of the total number of append() operations.
+    private long mCursor = 0;
+
+    public RingBuffer(Class<T> c, int capacity) {
+        checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
+        // Java cannot create generic arrays without a runtime hint.
+        mBuffer = (T[]) Array.newInstance(c, capacity);
+    }
+
+    public int size() {
+        return (int) Math.min(mBuffer.length, (long) mCursor);
+    }
+
+    public void append(T t) {
+        mBuffer[indexOf(mCursor++)] = t;
+    }
+
+    public T[] toArray() {
+        // Only generic way to create a T[] from another T[]
+        T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
+        // Reverse iteration from youngest event to oldest event.
+        long inCursor = mCursor - 1;
+        int outIdx = out.length - 1;
+        while (outIdx >= 0) {
+            out[outIdx--] = (T) mBuffer[indexOf(inCursor--)];
+        }
+        return out;
+    }
+
+    private int indexOf(long cursor) {
+        return (int) Math.abs(cursor % mBuffer.length);
+    }
+}
diff --git a/com/android/internal/widget/LinearLayoutManager.java b/com/android/internal/widget/LinearLayoutManager.java
index d82c746..0000a74 100644
--- a/com/android/internal/widget/LinearLayoutManager.java
+++ b/com/android/internal/widget/LinearLayoutManager.java
@@ -168,10 +168,6 @@
     /**
      * Constructor used when layout manager is set in XML by RecyclerView attribute
      * "layoutManager". Defaults to vertical orientation.
-     *
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
      */
     public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
diff --git a/com/android/internal/widget/LockPatternUtils.java b/com/android/internal/widget/LockPatternUtils.java
index b8ef82b..4be6b28 100644
--- a/com/android/internal/widget/LockPatternUtils.java
+++ b/com/android/internal/widget/LockPatternUtils.java
@@ -303,7 +303,7 @@
     }
 
     public void reportFailedPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -311,7 +311,7 @@
     }
 
     public void reportSuccessfulPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -319,21 +319,21 @@
     }
 
     public void reportPasswordLockout(int timeoutMs, int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getTrustManager().reportUnlockLockout(timeoutMs, userId);
     }
 
     public int getCurrentFailedPasswordAttempts(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return 0;
         }
         return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
     }
 
     public int getMaximumFailedPasswordsForWipe(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return 0;
         }
         return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -1774,11 +1774,12 @@
         return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
     }
 
-    public static boolean userOwnsFrpCredential(UserInfo info) {
-        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled();
+    public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
+        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
     }
 
-    public static boolean frpCredentialEnabled() {
-        return FRP_CREDENTIAL_ENABLED;
+    public static boolean frpCredentialEnabled(Context context) {
+        return FRP_CREDENTIAL_ENABLED && context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
     }
 }
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
new file mode 100644
index 0000000..86e7b38
--- /dev/null
+++ b/com/android/internal/widget/Magnifier.java
@@ -0,0 +1,184 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.PixelCopy;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+/**
+ * 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";
+    // The view for which this magnifier is attached.
+    private final View mView;
+    // The window containing the magnifier.
+    private final PopupWindow mWindow;
+    // The center coordinates of the window containing the magnifier.
+    private final Point mWindowCoords = new Point();
+    // The width of the window containing the magnifier.
+    private final int mWindowWidth;
+    // The height of the window containing the magnifier.
+    private final int mWindowHeight;
+    // The bitmap used to display the contents of the magnifier.
+    private final Bitmap mBitmap;
+    // The center coordinates of the content that is to be magnified.
+    private final Point mCenterZoomCoords = new Point();
+    // The callback of the pixel copy request will be invoked on this Handler when
+    // the copy is finished.
+    private final Handler mPixelCopyHandler = Handler.getMain();
+
+    /**
+     * Initializes a magnifier.
+     *
+     * @param view the view for which this magnifier is attached
+     */
+    @UiThread
+    public Magnifier(@NonNull View view) {
+        mView = Preconditions.checkNotNull(view);
+        final Context context = mView.getContext();
+        final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
+        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);
+
+        mWindow = new PopupWindow(context);
+        mWindow.setContentView(content);
+        mWindow.setWidth(mWindowWidth);
+        mWindow.setHeight(mWindowHeight);
+        mWindow.setElevation(elevation);
+        mWindow.setTouchable(false);
+        mWindow.setBackgroundDrawable(null);
+
+        mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888);
+        getImageView().setImageBitmap(mBitmap);
+    }
+
+    /**
+     * 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
+     */
+    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();
+
+        if (mWindow.isShowing()) {
+            mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
+                    mWindow.getHeight());
+        } else {
+            mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
+                    mWindowCoords.x, mWindowCoords.y);
+        }
+    }
+
+    /**
+     * Dismisses the magnifier from the screen.
+     */
+    public void dismiss() {
+        mWindow.dismiss();
+    }
+
+    /**
+     * @return the height of the magnifier window.
+     */
+    public int getHeight() {
+        return mWindowHeight;
+    }
+
+    /**
+     * @return the width of the magnifier window.
+     */
+    public int getWidth() {
+        return mWindowWidth;
+    }
+
+    private void maybeResizeBitmap(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);
+        }
+    }
+
+    private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
+        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);
+    }
+
+    private void performPixelCopy() {
+        int startX = 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();
+        }
+
+        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+        final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
+
+        if (viewRootImpl != null && viewRootImpl.mSurface != null
+                && viewRootImpl.mSurface.isValid()) {
+            PixelCopy.request(
+                    viewRootImpl.mSurface,
+                    new Rect(startX, startY, startX + mBitmap.getWidth(),
+                            startY + mBitmap.getHeight()),
+                    mBitmap,
+                    result -> getImageView().invalidate(),
+                    mPixelCopyHandler);
+        } else {
+            Log.d(LOG_TAG, "Could not perform PixelCopy request");
+        }
+    }
+
+    private ImageView getImageView() {
+        return mWindow.getContentView().findViewById(R.id.magnifier_image);
+    }
+}
diff --git a/com/android/keyguard/KeyguardDisplayManager.java b/com/android/keyguard/KeyguardDisplayManager.java
index 8de1d31..2bc0e45 100644
--- a/com/android/keyguard/KeyguardDisplayManager.java
+++ b/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.keyguard;
 
+import static android.view.Display.INVALID_DISPLAY;
+
 import android.app.Presentation;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -28,16 +30,21 @@
 import android.view.View;
 import android.view.WindowManager;
 
+// TODO(multi-display): Support multiple external displays
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
     private static boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private final ViewMediatorCallback mCallback;
+    private final MediaRouter mMediaRouter;
+    private final Context mContext;
+
     Presentation mPresentation;
-    private MediaRouter mMediaRouter;
-    private Context mContext;
     private boolean mShowing;
 
-    public KeyguardDisplayManager(Context context) {
+    public KeyguardDisplayManager(Context context, ViewMediatorCallback callback) {
         mContext = context;
+        mCallback = callback;
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
     }
 
@@ -90,6 +97,7 @@
     };
 
     protected void updateDisplays(boolean showing) {
+        Presentation originalPresentation = mPresentation;
         if (showing) {
             MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
                     MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
@@ -121,6 +129,13 @@
                 mPresentation = null;
             }
         }
+
+        // mPresentation is only updated when the display changes
+        if (mPresentation != originalPresentation) {
+            final int displayId = mPresentation != null
+                    ? mPresentation.getDisplay().getDisplayId() : INVALID_DISPLAY;
+            mCallback.onSecondaryDisplayShowingChanged(displayId);
+        }
     }
 
     private final static class KeyguardPresentation extends Presentation {
diff --git a/com/android/keyguard/KeyguardSimPinView.java b/com/android/keyguard/KeyguardSimPinView.java
index 7225ba9..432b406 100644
--- a/com/android/keyguard/KeyguardSimPinView.java
+++ b/com/android/keyguard/KeyguardSimPinView.java
@@ -66,7 +66,11 @@
                 // again when the PUK locked SIM is re-entered.
                 case ABSENT: {
                     KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
-                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    // onSimStateChanged callback can fire when the SIM PIN lock is not currently
+                    // active and mCallback is null.
+                    if (mCallback != null) {
+                        mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    }
                     break;
                 }
                 default:
diff --git a/com/android/keyguard/KeyguardSimPukView.java b/com/android/keyguard/KeyguardSimPukView.java
index 171cf23..7f79008 100644
--- a/com/android/keyguard/KeyguardSimPukView.java
+++ b/com/android/keyguard/KeyguardSimPukView.java
@@ -72,7 +72,11 @@
                 // move into the READY state and the PUK lock keyguard should be removed.
                 case READY: {
                     KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
-                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    // mCallback can be null if onSimStateChanged callback is called when keyguard
+                    // isn't active.
+                    if (mCallback != null) {
+                        mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    }
                     break;
                 }
                 default:
diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java
index d95402c..d83a6c6 100644
--- a/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
 import static android.os.BatteryManager.BATTERY_STATUS_FULL;
@@ -452,6 +454,7 @@
      */
     public void setKeyguardGoingAway(boolean goingAway) {
         mKeyguardGoingAway = goingAway;
+        updateFingerprintListeningState();
     }
 
     /**
@@ -1069,6 +1072,7 @@
                 cb.onDreamingStateChanged(mIsDreaming);
             }
         }
+        updateFingerprintListeningState();
     }
 
     /**
@@ -1772,7 +1776,7 @@
         public void onTaskStackChangedBackground() {
             try {
                 ActivityManager.StackInfo info = ActivityManager.getService().getStackInfo(
-                        ActivityManager.StackId.ASSISTANT_STACK_ID);
+                        WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
                 if (info == null) {
                     return;
                 }
diff --git a/com/android/keyguard/ViewMediatorCallback.java b/com/android/keyguard/ViewMediatorCallback.java
index 327d218..b194de4 100644
--- a/com/android/keyguard/ViewMediatorCallback.java
+++ b/com/android/keyguard/ViewMediatorCallback.java
@@ -88,4 +88,9 @@
      *         {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
      */
     int getBouncerPromptReason();
+
+    /**
+     * Invoked when the secondary display showing a keyguard window changes.
+     */
+    void onSecondaryDisplayShowingChanged(int displayId);
 }
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 5dca8e7..0cfc181 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 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.
@@ -14,659 +14,62 @@
  * limitations under the License.
  */
 
-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;
+package com.android.layoutlib.bridge;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 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;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
 
 /**
- * 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)}
+ * Legacy Bridge used in the SDK version of layoutlib
  */
 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 final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
+    private static class BridgeRenderSession extends RenderSession {
 
-    public static class StaticMethodNotImplementedException extends RuntimeException {
-        private static final long serialVersionUID = 1L;
+        @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 StaticMethodNotImplementedException(String msg) {
-            super(msg);
+            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;
         }
     }
 
-    /**
-     * 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();
 
-    /**
-     * 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 RenderSession createSession(SessionParams params) {
+        return new BridgeRenderSession();
+    }
 
     @Override
     public int getApiLevel() {
-        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));
-        }
+        return 0;
     }
 }
diff --git a/com/android/layoutlib/bridge/android/BridgeContext.java b/com/android/layoutlib/bridge/android/BridgeContext.java
index 4c6c9d4..4a75be9 100644
--- a/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -40,7 +40,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.app.SystemServiceRegistry_Accessor;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -86,7 +85,6 @@
 import android.view.BridgeInflater;
 import android.view.Display;
 import android.view.DisplayAdjustments;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -610,45 +608,35 @@
 
     @Override
     public Object getSystemService(String service) {
-        if (LAYOUT_INFLATER_SERVICE.equals(service)) {
-            return mBridgeInflater;
+        switch (service) {
+            case LAYOUT_INFLATER_SERVICE:
+                return mBridgeInflater;
+
+            case TEXT_SERVICES_MANAGER_SERVICE:
+                // we need to return a valid service to avoid NPE
+                return TextServicesManager.getInstance();
+
+            case WINDOW_SERVICE:
+                return mWindowManager;
+
+            case POWER_SERVICE:
+                return new PowerManager(this, new BridgePowerManager(), new Handler());
+
+            case DISPLAY_SERVICE:
+                return mDisplayManager;
+
+            case ACCESSIBILITY_SERVICE:
+                return AccessibilityManager.getInstance(this);
+
+            case INPUT_METHOD_SERVICE:  // needed by SearchView
+            case AUTOFILL_MANAGER_SERVICE:
+            case AUDIO_SERVICE:
+            case TEXT_CLASSIFICATION_SERVICE:
+                return null;
+            default:
+                assert false : "Unsupported Service: " + service;
         }
 
-        if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
-            // we need to return a valid service to avoid NPE
-            return TextServicesManager.getInstance();
-        }
-
-        if (WINDOW_SERVICE.equals(service)) {
-            return mWindowManager;
-        }
-
-        // needed by SearchView
-        if (INPUT_METHOD_SERVICE.equals(service)) {
-            return null;
-        }
-
-        if (POWER_SERVICE.equals(service)) {
-            return new PowerManager(this, new BridgePowerManager(), new Handler());
-        }
-
-        if (DISPLAY_SERVICE.equals(service)) {
-            return mDisplayManager;
-        }
-
-        if (ACCESSIBILITY_SERVICE.equals(service)) {
-            return AccessibilityManager.getInstance(this);
-        }
-
-        if (AUTOFILL_MANAGER_SERVICE.equals(service)) {
-            return null;
-        }
-
-        if (AUDIO_SERVICE.equals(service)) {
-            return null;
-        }
-
-        assert false : "Unsupported Service: " + service;
         return null;
     }
 
@@ -657,13 +645,13 @@
         return SystemServiceRegistry_Accessor.getSystemServiceName(serviceClass);
     }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
-        return obtainStyledAttributes(0, attrs);
-    }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs)
+    /**
+     * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+     * original Context the chance to override the theme when needed.
+     */
+    @Nullable
+    public final BridgeTypedArray internalObtainStyledAttributes(int resId, int[] attrs)
             throws Resources.NotFoundException {
         StyleResourceValue style = null;
         // get the StyleResourceValue based on the resId;
@@ -715,13 +703,12 @@
         return typeArrayAndPropertiesPair.getFirst();
     }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
-        return obtainStyledAttributes(set, attrs, 0, 0);
-    }
-
-    @Override
-    public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
+    /**
+     * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+     * original Context the chance to override the theme when needed.
+     */
+    @Nullable
+    public BridgeTypedArray internalObtainStyledAttributes(@Nullable AttributeSet set, int[] attrs,
             int defStyleAttr, int defStyleRes) {
 
         PropertiesMap defaultPropMap = null;
diff --git a/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index cdcf0ea..bc77685 100644
--- a/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -26,6 +26,7 @@
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,11 +34,18 @@
 import android.graphics.drawable.Drawable;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.List;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.resources.ResourceType.MENU;
 
 
 /**
@@ -50,6 +58,7 @@
     private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
     // This is used on v23.1.1 and later.
     private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
+
     private Class<?> mWindowActionBarClass;
 
     /**
@@ -90,6 +99,7 @@
                     constructorParams, constructorArgs);
             mWindowActionBarClass = mWindowDecorActionBar == null ? null :
                     mWindowDecorActionBar.getClass();
+            inflateMenus();
             setupActionBar();
         } catch (Exception e) {
             Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
@@ -165,6 +175,51 @@
         }
     }
 
+    private void inflateMenus() {
+        List<String> menuNames = getCallBack().getMenuIdNames();
+        if (menuNames.isEmpty()) {
+            return;
+        }
+
+        if (menuNames.size() > 1) {
+            // Supporting multiple menus means that we would need to instantiate our own supportlib
+            // MenuInflater instances using reflection
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                    "Support Toolbar does not currently support multiple menus in the preview.",
+                    null, null, null);
+        }
+
+        String name = menuNames.get(0);
+        int id;
+        if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
+            // Framework menu.
+            name = name.substring(ANDROID_NS_NAME_PREFIX.length());
+            id = mBridgeContext.getFrameworkResourceValue(MENU, name, -1);
+        } else {
+            // Project menu.
+            id = mBridgeContext.getProjectResourceValue(MENU, name, -1);
+        }
+        if (id < 1) {
+            return;
+        }
+        // Get toolbar decorator
+        Object mDecorToolbar = getFieldValue(mWindowDecorActionBar, "mDecorToolbar");
+        if (mDecorToolbar == null) {
+            return;
+        }
+
+        Class<?> mDecorToolbarClass = mDecorToolbar.getClass();
+        Context themedContext = (Context)invoke(
+                getMethod(mWindowActionBarClass, "getThemedContext"),
+                mWindowDecorActionBar);
+        MenuInflater inflater = new MenuInflater(themedContext);
+        Menu menuBuilder = (Menu)invoke(getMethod(mDecorToolbarClass, "getMenu"), mDecorToolbar);
+        inflater.inflate(id, menuBuilder);
+
+        // Set the actual menu
+        invoke(findMethod(mDecorToolbarClass, "setMenu"), mDecorToolbar, menuBuilder, null);
+    }
+
     @Override
     public void createMenuPopup() {
         // it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection.
@@ -181,13 +236,53 @@
         return null;
     }
 
+    /**
+     * Same as getMethod but doesn't require the parameterTypes. This allows us to call methods
+     * without having to get all the types for the parameters when we do not need them
+     */
     @Nullable
-    private static Object invoke(Method method, Object owner, Object... args) {
+    private static Method findMethod(@Nullable Class<?> owner, @NotNull String name) {
+        if (owner == null) {
+            return null;
+        }
+        for (Method method : owner.getMethods()) {
+            if (name.equals(method.getName())) {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
+    @Nullable
+    private static Object getFieldValue(@Nullable Object instance, @NotNull String name) {
+        if (instance == null) {
+            return null;
+        }
+
+        Class<?> instanceClass = instance.getClass();
+        try {
+            Field field = instanceClass.getDeclaredField(name);
+            boolean accesible = field.isAccessible();
+            if (!accesible) {
+                field.setAccessible(true);
+            }
+            try {
+                return field.get(instance);
+            } finally {
+                field.setAccessible(accesible);
+            }
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    @Nullable
+    private static Object invoke(@Nullable Method method, Object owner, Object... args) {
         try {
             return method == null ? null : method.invoke(owner, args);
-        } catch (InvocationTargetException e) {
-            e.printStackTrace();
-        } catch (IllegalAccessException e) {
+        } catch (InvocationTargetException | IllegalAccessException e) {
             e.printStackTrace();
         }
         return null;
diff --git a/com/android/layoutlib/bridge/impl/GcSnapshot.java b/com/android/layoutlib/bridge/impl/GcSnapshot.java
index 3ad859c..7526e09 100644
--- a/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -19,7 +19,6 @@
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
 
-import android.annotation.NonNull;
 import android.graphics.Bitmap_Delegate;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter_Delegate;
@@ -40,13 +39,11 @@
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Shape;
-import java.awt.Transparency;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
 import java.awt.geom.NoninvertibleTransformException;
 import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
-import java.lang.ref.SoftReference;
 import java.util.ArrayList;
 
 /**
@@ -69,7 +66,7 @@
     private final int mFlags;
 
     /** list of layers. The first item in the list is always the  */
-    private final ArrayList<Layer> mLayers = new ArrayList<>();
+    private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
 
     /** temp transform in case transformation are set before a Graphics2D exists */
     private AffineTransform mTransform = null;
@@ -85,13 +82,6 @@
     private final Layer mLocalLayer;
     private final Paint_Delegate mLocalLayerPaint;
     private final Rect mLayerBounds;
-    /**
-     * Cached buffer to be used for tinting operations. This buffer is usually used many times
-     * and there is no need to creating it every time.
-     */
-    private SoftReference<BufferedImage> mCachedLayerBuffer = new SoftReference<>(null);
-    private Rectangle2D.Float mCachedClipRect = new Rectangle2D.Float();
-
 
     public interface Drawable {
         void draw(Graphics2D graphics, Paint_Delegate paint);
@@ -310,11 +300,12 @@
             Layer baseLayer = mLayers.get(0);
 
             // create the image for the layer
-            BufferedImage layerImage =
-                    baseLayer.getGraphics().getDeviceConfiguration().createCompatibleImage(
-                            baseLayer.getImage().getWidth(), baseLayer.getImage().getHeight(),
-                            (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
-                                    Transparency.TRANSLUCENT : Transparency.OPAQUE);
+            BufferedImage layerImage = new BufferedImage(
+                    baseLayer.getImage().getWidth(),
+                    baseLayer.getImage().getHeight(),
+                    (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
+                            BufferedImage.TYPE_INT_ARGB :
+                                BufferedImage.TYPE_INT_RGB);
 
             // create a graphics for it so that drawing can be done.
             Graphics2D layerGraphics = layerImage.createGraphics();
@@ -361,8 +352,6 @@
     }
 
     public void dispose() {
-        mCachedLayerBuffer.clear();
-
         for (Layer layer : mLayers) {
             layer.getGraphics().dispose();
         }
@@ -538,12 +527,7 @@
     }
 
     public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
-        if (mCachedClipRect == null) {
-            mCachedClipRect = new Rectangle2D.Float(left, top, right - left, bottom - top);
-        } else {
-            mCachedClipRect.setRect(left, top, right - left, bottom - top);
-        }
-        return clip(mCachedClipRect, regionOp);
+        return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
     }
 
     /**
@@ -656,16 +640,17 @@
                 height = layer.getImage().getHeight();
             }
 
+            // Create a temporary image to which the color filter will be applied.
+            BufferedImage image = new BufferedImage(width, height,
+                    BufferedImage.TYPE_INT_ARGB);
+            Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
+            // Configure the Graphics2D object with drawing parameters and shader.
+            Graphics2D imageGraphics = createCustomGraphics(
+                    imageBaseGraphics, paint, compositeOnly,
+                    AlphaComposite.SRC_OVER);
             // get a Graphics2D object configured with the drawing parameters, but no shader.
             Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
                     true /*compositeOnly*/, forceMode);
-
-            // Create or re-use a temporary image to which the color filter will be applied.
-            BufferedImage image = getTemporaryBuffer(configuredGraphics, width, height);
-            Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
-            // Configure the Graphics2D object with drawing parameters and shader.
-            Graphics2D imageGraphics = createCustomGraphics(imageBaseGraphics, paint, compositeOnly,
-                    AlphaComposite.SRC_OVER);
             try {
                 // The main draw operation.
                 // We translate the operation to take into account that the rendering does not
@@ -692,28 +677,6 @@
         }
     }
 
-    /**
-     * Returns a temporary buffer sized width * height and configured with the given
-     * {@link Graphics2D} device configuration.
-     */
-    @NonNull
-    private BufferedImage getTemporaryBuffer(@NonNull Graphics2D configuredGraphics,
-            int width, int height) {
-        BufferedImage cachedImage = mCachedLayerBuffer.get();
-        if (cachedImage == null ||
-                width > cachedImage.getWidth() || height > cachedImage.getHeight() ||
-                !configuredGraphics.getDeviceConfiguration().getColorModel()
-                        .isCompatibleSampleModel(cachedImage.getSampleModel())) {
-            // The current cached image is not valid or does not exist
-            cachedImage = configuredGraphics.getDeviceConfiguration()
-                    .createCompatibleImage(width, height);
-            mCachedLayerBuffer = new SoftReference<>(cachedImage);
-        } else {
-            cachedImage = cachedImage.getSubimage(0, 0, width, height);
-        }
-        return cachedImage;
-    }
-
     private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
             Layer layer) {
         try {
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index ec6f831..67fb4d9 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -720,9 +720,6 @@
                 Settings.Global.DEVICE_IDLE_CONSTANTS,
                 GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
         dumpSetting(s, p,
-                Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
-                GlobalSettingsProto.DEVICE_IDLE_CONSTANTS_WATCH);
-        dumpSetting(s, p,
                 Settings.Global.APP_IDLE_CONSTANTS,
                 GlobalSettingsProto.APP_IDLE_CONSTANTS);
         dumpSetting(s, p,
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index a463db6..36f9b84 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -2896,7 +2896,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 149;
+            private static final int SETTINGS_VERSION = 150;
 
             private final int mUserId;
 
@@ -3470,9 +3470,25 @@
                                     true, SettingsState.SYSTEM_PACKAGE_NAME);
                         }
                     }
-
                     currentVersion = 149;
                 }
+
+                if (currentVersion == 149) {
+                    // Version 150: Set a default value for mobile data always on
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting currentSetting = globalSettings.getSettingLocked(
+                            Settings.Global.MOBILE_DATA_ALWAYS_ON);
+                    if (currentSetting.isNull()) {
+                        globalSettings.insertSettingLocked(
+                                Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                                getContext().getResources().getBoolean(
+                                        R.bool.def_mobile_data_always_on) ? "1" : "0",
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 150;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 83bd9eb..5106c8d 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -20,6 +20,7 @@
 import android.database.ContentObserver;
 import android.os.BatteryStats;
 
+import android.os.PowerManager;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
@@ -291,6 +292,8 @@
                     if (mActivityManagerInternal.isSystemReady()) {
                         Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                         intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.putExtra(Intent.EXTRA_REASON,
+                                PowerManager.SHUTDOWN_LOW_BATTERY);
                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                     }
@@ -310,6 +313,8 @@
                     if (mActivityManagerInternal.isSystemReady()) {
                         Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                         intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.putExtra(Intent.EXTRA_REASON,
+                                PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE);
                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                     }
diff --git a/com/android/server/ConnectivityService.java b/com/android/server/ConnectivityService.java
index bfe5040..348c799 100644
--- a/com/android/server/ConnectivityService.java
+++ b/com/android/server/ConnectivityService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -90,6 +91,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -128,7 +130,6 @@
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
-import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkDiagnostics;
 import com.android.server.connectivity.NetworkMonitor;
@@ -781,6 +782,13 @@
             mNetworksDefined++;  // used only in the log() statement below.
         }
 
+        // Do the same for Ethernet, since it's often not specified in the configs, although many
+        // devices can use it via USB host adapters.
+        if (mNetConfigs[TYPE_ETHERNET] == null && hasService(Context.ETHERNET_SERVICE)) {
+            mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
+            mNetworksDefined++;
+        }
+
         if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
 
         mProtectedNetworks = new ArrayList<Integer>();
@@ -2205,7 +2213,7 @@
                 // A network factory has connected.  Send it all current NetworkRequests.
                 for (NetworkRequestInfo nri : mNetworkRequests.values()) {
                     if (nri.request.isListen()) continue;
-                    NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+                    NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
                     ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
                             (nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
                 }
@@ -2282,9 +2290,9 @@
             // Remove all previously satisfied requests.
             for (int i = 0; i < nai.numNetworkRequests(); i++) {
                 NetworkRequest request = nai.requestAt(i);
-                NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
+                NetworkAgentInfo currentNetwork = getNetworkForRequest(request.requestId);
                 if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
-                    mNetworkForRequestId.remove(request.requestId);
+                    clearNetworkForRequest(request.requestId);
                     sendUpdatedScoreToFactories(request, 0);
                 }
             }
@@ -2360,7 +2368,7 @@
             }
         }
         rematchAllNetworksAndRequests(null, 0);
-        if (nri.request.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
+        if (nri.request.isRequest() && getNetworkForRequest(nri.request.requestId) == null) {
             sendUpdatedScoreToFactories(nri.request, 0);
         }
     }
@@ -2415,7 +2423,7 @@
                     // 2. Unvalidated WiFi will not be reaped when validated cellular
                     //    is currently satisfying the request.  This is desirable when
                     //    WiFi ends up validating and out scoring cellular.
-                    mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
+                    getNetworkForRequest(nri.request.requestId).getCurrentScore() <
                             nai.getCurrentScoreAsValidated())) {
                 return false;
             }
@@ -2442,7 +2450,7 @@
         if (mNetworkRequests.get(nri.request) == null) {
             return;
         }
-        if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+        if (getNetworkForRequest(nri.request.requestId) != null) {
             return;
         }
         if (VDBG || (DBG && nri.request.isRequest())) {
@@ -2482,7 +2490,7 @@
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
         if (nri.request.isRequest()) {
             boolean wasKept = false;
-            NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+            NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
             if (nai != null) {
                 boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
                 nai.removeRequest(nri.request.requestId);
@@ -2499,7 +2507,7 @@
                 } else {
                     wasKept = true;
                 }
-                mNetworkForRequestId.remove(nri.request.requestId);
+                clearNetworkForRequest(nri.request.requestId);
                 if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
                     // Went from foreground to background.
                     updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -4296,7 +4304,8 @@
      * and the are the highest scored network available.
      * the are keyed off the Requests requestId.
      */
-    // TODO: Yikes, this is accessed on multiple threads: add synchronization.
+    // NOTE: Accessed on multiple threads, must be synchronized on itself.
+    @GuardedBy("mNetworkForRequestId")
     private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
             new SparseArray<NetworkAgentInfo>();
 
@@ -4326,8 +4335,26 @@
     // priority networks like Wi-Fi are active.
     private final NetworkRequest mDefaultMobileDataRequest;
 
+    private NetworkAgentInfo getNetworkForRequest(int requestId) {
+        synchronized (mNetworkForRequestId) {
+            return mNetworkForRequestId.get(requestId);
+        }
+    }
+
+    private void clearNetworkForRequest(int requestId) {
+        synchronized (mNetworkForRequestId) {
+            mNetworkForRequestId.remove(requestId);
+        }
+    }
+
+    private void setNetworkForRequest(int requestId, NetworkAgentInfo nai) {
+        synchronized (mNetworkForRequestId) {
+            mNetworkForRequestId.put(requestId, nai);
+        }
+    }
+
     private NetworkAgentInfo getDefaultNetwork() {
-        return mNetworkForRequestId.get(mDefaultRequest.requestId);
+        return getNetworkForRequest(mDefaultRequest.requestId);
     }
 
     private boolean isDefaultNetwork(NetworkAgentInfo nai) {
@@ -4881,7 +4908,7 @@
             // requests or not, and doesn't affect the network's score.
             if (nri.request.isListen()) continue;
 
-            final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+            final NetworkAgentInfo currentNetwork = getNetworkForRequest(nri.request.requestId);
             final boolean satisfies = newNetwork.satisfies(nri.request);
             if (newNetwork == currentNetwork && satisfies) {
                 if (VDBG) {
@@ -4913,7 +4940,7 @@
                         if (VDBG) log("   accepting network in place of null");
                     }
                     newNetwork.unlingerRequest(nri.request);
-                    mNetworkForRequestId.put(nri.request.requestId, newNetwork);
+                    setNetworkForRequest(nri.request.requestId, newNetwork);
                     if (!newNetwork.addRequest(nri.request)) {
                         Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);
                     }
@@ -4947,7 +4974,7 @@
                 }
                 newNetwork.removeRequest(nri.request.requestId);
                 if (currentNetwork == newNetwork) {
-                    mNetworkForRequestId.remove(nri.request.requestId);
+                    clearNetworkForRequest(nri.request.requestId);
                     sendUpdatedScoreToFactories(nri.request, 0);
                 } else {
                     Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
@@ -5522,6 +5549,11 @@
         return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
     }
 
+    @VisibleForTesting
+    public boolean hasService(String name) {
+        return ServiceManager.checkService(name) != null;
+    }
+
     private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
         int newNetid = NETID_UNSET;
         int prevNetid = NETID_UNSET;
diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java
index abbc89e..2d9baf6 100644
--- a/com/android/server/DeviceIdleController.java
+++ b/com/android/server/DeviceIdleController.java
@@ -307,6 +307,12 @@
      */
     private int[] mTempWhitelistAppIdArray = new int[0];
 
+    /**
+     * Apps in the system whitelist that have been taken out (probably because the user wanted to).
+     * They can be restored back by calling restoreAppToSystemWhitelist(String).
+     */
+    private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();
+
     private static final int EVENT_NULL = 0;
     private static final int EVENT_NORMAL = 1;
     private static final int EVENT_LIGHT_IDLE = 2;
@@ -760,17 +766,15 @@
         public long NOTIFICATION_WHITELIST_DURATION;
 
         private final ContentResolver mResolver;
-        private final boolean mHasWatch;
+        private final boolean mSmallBatteryDevice;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         public Constants(Handler handler, ContentResolver resolver) {
             super(handler);
             mResolver = resolver;
-            mHasWatch = getContext().getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WATCH);
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                              : Settings.Global.DEVICE_IDLE_CONSTANTS),
+            mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice();
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS),
                     false, this);
             updateConstants();
         }
@@ -784,8 +788,7 @@
             synchronized (DeviceIdleController.this) {
                 try {
                     mParser.setString(Settings.Global.getString(mResolver,
-                            mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                                      : Settings.Global.DEVICE_IDLE_CONSTANTS));
+                            Settings.Global.DEVICE_IDLE_CONSTANTS));
                 } catch (IllegalArgumentException e) {
                     // Failed to parse the settings string, log this and move on
                     // with defaults.
@@ -815,7 +818,7 @@
                 MIN_DEEP_MAINTENANCE_TIME = mParser.getLong(
                         KEY_MIN_DEEP_MAINTENANCE_TIME,
                         !COMPRESS_TIME ? 30 * 1000L : 5 * 1000L);
-                long inactiveTimeoutDefault = (mHasWatch ? 15 : 30) * 60 * 1000L;
+                long inactiveTimeoutDefault = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
                 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? inactiveTimeoutDefault : (inactiveTimeoutDefault / 10));
                 SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -825,7 +828,7 @@
                 LOCATION_ACCURACY = mParser.getFloat(KEY_LOCATION_ACCURACY, 20);
                 MOTION_INACTIVE_TIMEOUT = mParser.getLong(KEY_MOTION_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L);
-                long idleAfterInactiveTimeout = (mHasWatch ? 15 : 30) * 60 * 1000L;
+                long idleAfterInactiveTimeout = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
                 IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? idleAfterInactiveTimeout
                                        : (idleAfterInactiveTimeout / 10));
@@ -1162,6 +1165,38 @@
             }
         }
 
+        @Override public void removeSystemPowerWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.d(TAG, "removeAppFromSystemWhitelist(name = " + name + ")");
+            }
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                removeSystemPowerWhitelistAppInternal(name);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override public void restoreSystemPowerWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.d(TAG, "restoreAppToSystemWhitelist(name = " + name + ")");
+            }
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                restoreSystemPowerWhitelistAppInternal(name);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        public String[] getRemovedSystemPowerWhitelistApps() {
+            return getRemovedSystemPowerWhitelistAppsInternal();
+        }
+
         @Override public String[] getSystemPowerWhitelistExceptIdle() {
             return getSystemPowerWhitelistExceptIdleInternal();
         }
@@ -1504,6 +1539,42 @@
         }
     }
 
+    void resetSystemPowerWhitelistInternal() {
+        synchronized (this) {
+            mPowerSaveWhitelistApps.putAll(mRemovedFromSystemWhitelistApps);
+            mRemovedFromSystemWhitelistApps.clear();
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+        }
+    }
+
+    public boolean restoreSystemPowerWhitelistAppInternal(String name) {
+        synchronized (this) {
+            if (!mRemovedFromSystemWhitelistApps.containsKey(name)) {
+                return false;
+            }
+            mPowerSaveWhitelistApps.put(name, mRemovedFromSystemWhitelistApps.remove(name));
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+            return true;
+        }
+    }
+
+    public boolean removeSystemPowerWhitelistAppInternal(String name) {
+        synchronized (this) {
+            if (!mPowerSaveWhitelistApps.containsKey(name)) {
+                return false;
+            }
+            mRemovedFromSystemWhitelistApps.put(name, mPowerSaveWhitelistApps.remove(name));
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+            return true;
+        }
+    }
+
     public boolean addPowerSaveWhitelistExceptIdleInternal(String name) {
         synchronized (this) {
             try {
@@ -1565,6 +1636,17 @@
         }
     }
 
+    public String[] getRemovedSystemPowerWhitelistAppsInternal() {
+        synchronized (this) {
+            int size = mRemovedFromSystemWhitelistApps.size();
+            final String[] apps = new String[size];
+            for (int i = 0; i < size; i++) {
+                apps[i] = mRemovedFromSystemWhitelistApps.keyAt(i);
+            }
+            return apps;
+        }
+    }
+
     public String[] getUserPowerWhitelistInternal() {
         synchronized (this) {
             int size = mPowerSaveWhitelistUserApps.size();
@@ -2481,21 +2563,31 @@
                 }
 
                 String tagName = parser.getName();
-                if (tagName.equals("wl")) {
-                    String name = parser.getAttributeValue(null, "n");
-                    if (name != null) {
-                        try {
-                            ApplicationInfo ai = pm.getApplicationInfo(name,
-                                    PackageManager.MATCH_ANY_USER);
-                            mPowerSaveWhitelistUserApps.put(ai.packageName,
-                                    UserHandle.getAppId(ai.uid));
-                        } catch (PackageManager.NameNotFoundException e) {
+                switch (tagName) {
+                    case "wl":
+                        String name = parser.getAttributeValue(null, "n");
+                        if (name != null) {
+                            try {
+                                ApplicationInfo ai = pm.getApplicationInfo(name,
+                                        PackageManager.MATCH_ANY_USER);
+                                mPowerSaveWhitelistUserApps.put(ai.packageName,
+                                        UserHandle.getAppId(ai.uid));
+                            } catch (PackageManager.NameNotFoundException e) {
+                            }
                         }
-                    }
-                } else {
-                    Slog.w(TAG, "Unknown element under <config>: "
-                            + parser.getName());
-                    XmlUtils.skipCurrentTag(parser);
+                        break;
+                    case "un-wl":
+                        final String packageName = parser.getAttributeValue(null, "n");
+                        if (mPowerSaveWhitelistApps.containsKey(packageName)) {
+                            mRemovedFromSystemWhitelistApps.put(packageName,
+                                    mPowerSaveWhitelistApps.remove(packageName));
+                        }
+                        break;
+                    default:
+                        Slog.w(TAG, "Unknown element under <config>: "
+                                + parser.getName());
+                        XmlUtils.skipCurrentTag(parser);
+                        break;
                 }
             }
 
@@ -2556,6 +2648,11 @@
             out.attribute(null, "n", name);
             out.endTag(null, "wl");
         }
+        for (int i = 0; i < mRemovedFromSystemWhitelistApps.size(); i++) {
+            out.startTag(null, "un-wl");
+            out.attribute(null, "n", mRemovedFromSystemWhitelistApps.keyAt(i));
+            out.endTag(null, "un-wl");
+        }
         out.endTag(null, "config");
         out.endDocument();
     }
@@ -2584,6 +2681,13 @@
         pw.println("    Print currently whitelisted apps.");
         pw.println("  whitelist [package ...]");
         pw.println("    Add (prefix with +) or remove (prefix with -) packages.");
+        pw.println("  sys-whitelist [package ...|reset]");
+        pw.println("    Prefix the package with '-' to remove it from the system whitelist or '+'"
+                + " to put it back in the system whitelist.");
+        pw.println("    Note that only packages that were"
+                + " earlier removed from the system whitelist can be added back.");
+        pw.println("    reset will reset the whitelist to the original state");
+        pw.println("    Prints the system whitelist if no arguments are specified");
         pw.println("  except-idle-whitelist [package ...|reset]");
         pw.println("    Prefix the package with '+' to add it to whitelist or "
                 + "'=' to check if it is already whitelisted");
@@ -2944,6 +3048,50 @@
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
+        } else if ("sys-whitelist".equals(cmd)) {
+            String arg = shell.getNextArg();
+            if (arg != null) {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    if ("reset".equals(arg)) {
+                        resetSystemPowerWhitelistInternal();
+                    } else {
+                        do {
+                            if (arg.length() < 1
+                                    || (arg.charAt(0) != '-' && arg.charAt(0) != '+')) {
+                                pw.println("Package must be prefixed with + or - " + arg);
+                                return -1;
+                            }
+                            final char op = arg.charAt(0);
+                            final String pkg = arg.substring(1);
+                            switch (op) {
+                                case '+':
+                                    if (restoreSystemPowerWhitelistAppInternal(pkg)) {
+                                        pw.println("Restored " + pkg);
+                                    }
+                                    break;
+                                case '-':
+                                    if (removeSystemPowerWhitelistAppInternal(pkg)) {
+                                        pw.println("Removed " + pkg);
+                                    }
+                                    break;
+                            }
+                        } while ((arg = shell.getNextArg()) != null);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            } else {
+                synchronized (this) {
+                    for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
+                        pw.print(mPowerSaveWhitelistApps.keyAt(j));
+                        pw.print(",");
+                        pw.println(mPowerSaveWhitelistApps.valueAt(j));
+                    }
+                }
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
@@ -3027,6 +3175,14 @@
                     pw.println(mPowerSaveWhitelistApps.keyAt(i));
                 }
             }
+            size = mRemovedFromSystemWhitelistApps.size();
+            if (size > 0) {
+                pw.println("  Removed from whitelist system apps:");
+                for (int i = 0; i < size; i++) {
+                    pw.print("    ");
+                    pw.println(mRemovedFromSystemWhitelistApps.keyAt(i));
+                }
+            }
             size = mPowerSaveWhitelistUserApps.size();
             if (size > 0) {
                 pw.println("  Whitelist user apps:");
diff --git a/com/android/server/DiskStatsService.java b/com/android/server/DiskStatsService.java
index 800081e..2d2c6b0 100644
--- a/com/android/server/DiskStatsService.java
+++ b/com/android/server/DiskStatsService.java
@@ -202,6 +202,8 @@
             JSONObject json = new JSONObject(jsonString);
             pw.print("App Size: ");
             pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+            pw.print("App Data Size: ");
+            pw.println(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
             pw.print("App Cache Size: ");
             pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
             pw.print("Photos Size: ");
@@ -220,6 +222,8 @@
             pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
             pw.print("App Sizes: ");
             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
+            pw.print("App Data Sizes: ");
+            pw.println(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY));
             pw.print("Cache Sizes: ");
             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
         } catch (IOException | JSONException e) {
@@ -235,6 +239,8 @@
 
             proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE,
                     json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+            proto.write(DiskStatsCachedValuesProto.AGG_APPS_DATA_SIZE,
+                    json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
             proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE,
                     json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
             proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE,
@@ -252,22 +258,26 @@
 
             JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
             JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+            JSONArray appDataSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
             JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
             final int len = packageNamesArray.length();
-            if (len == appSizesArray.length() && len == cacheSizesArray.length()) {
+            if (len == appSizesArray.length()
+                    && len == appDataSizesArray.length()
+                    && len == cacheSizesArray.length()) {
                 for (int i = 0; i < len; i++) {
                     long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
 
                     proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
                             packageNamesArray.getString(i));
                     proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i));
+                    proto.write(DiskStatsAppSizesProto.APP_DATA_SIZE, appDataSizesArray.getLong(i));
                     proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i));
 
                     proto.end(packageToken);
                 }
             } else {
-                Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray and cacheSizesArray "
-                        + "are not the same");
+                Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray, appDataSizesArray "
+                        + " and cacheSizesArray are not the same");
             }
 
             proto.end(cachedValuesToken);
diff --git a/com/android/server/IpSecService.java b/com/android/server/IpSecService.java
index 3056831..2e1f142 100644
--- a/com/android/server/IpSecService.java
+++ b/com/android/server/IpSecService.java
@@ -33,6 +33,7 @@
 import android.net.IpSecTransform;
 import android.net.IpSecTransformResponse;
 import android.net.IpSecUdpEncapResponse;
+import android.net.NetworkUtils;
 import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.IBinder;
@@ -42,11 +43,14 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -54,6 +58,7 @@
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import libcore.io.IoUtils;
 
 /** @hide */
@@ -252,7 +257,11 @@
             return (mReferenceCount.get() > 0);
         }
 
-        public void checkOwnerOrSystemAndThrow() {
+        /**
+         * Ensures that the caller is either the owner of this resource or has the system UID and
+         * throws a SecurityException otherwise.
+         */
+        public void checkOwnerOrSystem() {
             if (uid != Binder.getCallingUid()
                     && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
                 throw new SecurityException("Only the owner may access managed resources!");
@@ -335,12 +344,12 @@
     private class ManagedResourceArray<T extends ManagedResource> {
         SparseArray<T> mArray = new SparseArray<>();
 
-        T get(int key) {
+        T getAndCheckOwner(int key) {
             T val = mArray.get(key);
             // The value should never be null unless the resource doesn't exist
             // (since we do not allow null resources to be added).
             if (val != null) {
-                val.checkOwnerOrSystemAndThrow();
+                val.checkOwnerOrSystem();
             }
             return val;
         }
@@ -405,12 +414,8 @@
                             .ipSecDeleteSecurityAssociation(
                                     mResourceId,
                                     direction,
-                                    (mConfig.getLocalAddress() != null)
-                                            ? mConfig.getLocalAddress().getHostAddress()
-                                            : "",
-                                    (mConfig.getRemoteAddress() != null)
-                                            ? mConfig.getRemoteAddress().getHostAddress()
-                                            : "",
+                                    mConfig.getLocalAddress(),
+                                    mConfig.getRemoteAddress(),
                                     spi);
                 } catch (ServiceSpecificException e) {
                     // FIXME: get the error code and throw is at an IOException from Errno Exception
@@ -638,11 +643,45 @@
         }
     }
 
+    /**
+     * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
+     * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
+     */
+    private static void checkInetAddress(String inetAddress) {
+        if (TextUtils.isEmpty(inetAddress)) {
+            throw new IllegalArgumentException("Unspecified address");
+        }
+
+        InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
+
+        if (checkAddr.isAnyLocalAddress()) {
+            throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
+        }
+    }
+
+    /**
+     * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
+     * DIRECTION_IN or DIRECTION_OUT
+     */
+    private static void checkDirection(int direction) {
+        switch (direction) {
+            case IpSecTransform.DIRECTION_OUT:
+            case IpSecTransform.DIRECTION_IN:
+                return;
+        }
+        throw new IllegalArgumentException("Invalid Direction: " + direction);
+    }
+
     @Override
     /** Get a new SPI and maintain the reservation in the system server */
     public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
             int direction, String remoteAddress, int requestedSpi, IBinder binder)
             throws RemoteException {
+        checkDirection(direction);
+        checkInetAddress(remoteAddress);
+        /* requestedSpi can be anything in the int range, so no check is needed. */
+        checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex");
+
         int resourceId = mNextResourceId.getAndIncrement();
 
         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
@@ -651,9 +690,7 @@
         try {
             if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).spi.isAvailable()) {
                 return new IpSecSpiResponse(
-                        IpSecManager.Status.RESOURCE_UNAVAILABLE,
-                        INVALID_RESOURCE_ID,
-                        spi);
+                        IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
             }
             spi =
                     mSrvConfig
@@ -686,7 +723,7 @@
             throws RemoteException {
         // We want to non-destructively get so that we can check credentials before removing
         // this from the records.
-        T record = resArray.get(resourceId);
+        T record = resArray.getAndCheckOwner(resourceId);
 
         if (record == null) {
             throw new IllegalArgumentException(
@@ -751,6 +788,8 @@
             throw new IllegalArgumentException(
                     "Specified port number must be a valid non-reserved UDP port");
         }
+        checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
+
         int resourceId = mNextResourceId.getAndIncrement();
         FileDescriptor sockFd = null;
         try {
@@ -792,6 +831,68 @@
     }
 
     /**
+     * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
+     * IllegalArgumentException if they are not.
+     */
+    private void checkIpSecConfig(IpSecConfig config) {
+        if (config.getLocalAddress() == null) {
+            throw new IllegalArgumentException("Invalid null Local InetAddress");
+        }
+
+        if (config.getRemoteAddress() == null) {
+            throw new IllegalArgumentException("Invalid null Remote InetAddress");
+        }
+
+        switch (config.getMode()) {
+            case IpSecTransform.MODE_TRANSPORT:
+                if (!config.getLocalAddress().isEmpty()) {
+                    throw new IllegalArgumentException("Non-empty Local Address");
+                }
+                // Must be valid, and not a wildcard
+                checkInetAddress(config.getRemoteAddress());
+                break;
+            case IpSecTransform.MODE_TUNNEL:
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid IpSecTransform.mode: " + config.getMode());
+        }
+
+        switch (config.getEncapType()) {
+            case IpSecTransform.ENCAP_NONE:
+                break;
+            case IpSecTransform.ENCAP_ESPINUDP:
+            case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
+                if (mUdpSocketRecords.getAndCheckOwner(
+                            config.getEncapSocketResourceId()) == null) {
+                    throw new IllegalStateException(
+                            "No Encapsulation socket for Resource Id: "
+                                    + config.getEncapSocketResourceId());
+                }
+
+                int port = config.getEncapRemotePort();
+                if (port <= 0 || port > 0xFFFF) {
+                    throw new IllegalArgumentException("Invalid remote UDP port: " + port);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
+        }
+
+        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");
+            }
+
+            if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) {
+                throw new IllegalStateException("No SPI for specified Resource Id");
+            }
+        }
+    }
+
+    /**
      * Create a transport mode transform, which represent two security associations (one in each
      * direction) in the kernel. The transform will be cached by the system server and must be freed
      * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
@@ -801,17 +902,19 @@
     @Override
     public synchronized IpSecTransformResponse createTransportModeTransform(
             IpSecConfig c, IBinder binder) throws RemoteException {
+        checkIpSecConfig(c);
+        checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
         int resourceId = mNextResourceId.getAndIncrement();
         if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).transform.isAvailable()) {
             return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
         SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
-        // TODO: Basic input validation here since it's coming over the Binder
+
         int encapType, encapLocalPort = 0, encapRemotePort = 0;
         UdpSocketRecord socketRecord = null;
         encapType = c.getEncapType();
         if (encapType != IpSecTransform.ENCAP_NONE) {
-            socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
+            socketRecord = mUdpSocketRecords.getAndCheckOwner(c.getEncapSocketResourceId());
             encapLocalPort = socketRecord.getPort();
             encapRemotePort = c.getEncapRemotePort();
         }
@@ -820,23 +923,18 @@
             IpSecAlgorithm auth = c.getAuthentication(direction);
             IpSecAlgorithm crypt = c.getEncryption(direction);
 
-            spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
+            spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction));
             int spi = spis[direction].getSpi();
             try {
-                mSrvConfig.getNetdInstance()
+                mSrvConfig
+                        .getNetdInstance()
                         .ipSecAddSecurityAssociation(
                                 resourceId,
                                 c.getMode(),
                                 direction,
-                                (c.getLocalAddress() != null)
-                                        ? c.getLocalAddress().getHostAddress()
-                                        : "",
-                                (c.getRemoteAddress() != null)
-                                        ? c.getRemoteAddress().getHostAddress()
-                                        : "",
-                                (c.getNetwork() != null)
-                                        ? c.getNetwork().getNetworkHandle()
-                                        : 0,
+                                c.getLocalAddress(),
+                                c.getRemoteAddress(),
+                                (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
                                 spi,
                                 (auth != null) ? auth.getName() : "",
                                 (auth != null) ? auth.getKey() : null,
@@ -879,7 +977,7 @@
         // Synchronize liberally here because we are using ManagedResources in this block
         TransformRecord info;
         // FIXME: this code should be factored out into a security check + getter
-        info = mTransformRecords.get(resourceId);
+        info = mTransformRecords.getAndCheckOwner(resourceId);
 
         if (info == null) {
             throw new IllegalArgumentException("Transform " + resourceId + " is not active");
@@ -899,12 +997,8 @@
                                 socket.getFileDescriptor(),
                                 resourceId,
                                 direction,
-                                (c.getLocalAddress() != null)
-                                        ? c.getLocalAddress().getHostAddress()
-                                        : "",
-                                (c.getRemoteAddress() != null)
-                                        ? c.getRemoteAddress().getHostAddress()
-                                        : "",
+                                c.getLocalAddress(),
+                                c.getRemoteAddress(),
                                 info.getSpiRecord(direction).getSpi());
             }
         } catch (ServiceSpecificException e) {
diff --git a/com/android/server/LocationManagerService.java b/com/android/server/LocationManagerService.java
index 340d672..0fd59ea 100644
--- a/com/android/server/LocationManagerService.java
+++ b/com/android/server/LocationManagerService.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.annotation.NonNull;
-import android.content.pm.PackageManagerInternal;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import com.android.internal.content.PackageMonitor;
@@ -56,6 +55,7 @@
 import android.content.pm.ApplicationInfo;
 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.ResolveInfo;
 import android.content.pm.Signature;
diff --git a/com/android/server/NetworkManagementService.java b/com/android/server/NetworkManagementService.java
index 2f95aa2..ba3afc3 100644
--- a/com/android/server/NetworkManagementService.java
+++ b/com/android/server/NetworkManagementService.java
@@ -1140,17 +1140,6 @@
     }
 
     @Override
-    public void setInterfaceIpv6NdOffload(String iface, boolean enable) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute(
-                    "interface", "ipv6ndoffload", iface, (enable ? "enable" : "disable"));
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
     public void addRoute(int netId, RouteInfo route) {
         modifyRoute("add", "" + netId, route);
     }
@@ -1991,8 +1980,12 @@
 
         final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
         final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
+        final boolean useTls = false;
+        final String tlsHostname = "";
+        final String[] tlsFingerprints = new String[0];
         try {
-            mNetdService.setResolverConfiguration(netId, servers, domainStrs, params);
+            mNetdService.setResolverConfiguration(netId, servers, domainStrs, params,
+                    useTls, tlsHostname, tlsFingerprints);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index c0fcfd0..55391b3 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -111,8 +111,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.NativeDaemonConnector.Command;
-import com.android.server.NativeDaemonConnector.SensitiveArg;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.storage.AppFuseBridge;
 
@@ -138,7 +136,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -161,8 +158,7 @@
  * watch for and manage dynamically added storage, such as SD cards and USB mass
  * storage. Also decides how storage should be presented to users on the device.
  */
-class StorageManagerService extends IStorageManager.Stub
-        implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+class StorageManagerService extends IStorageManager.Stub implements Watchdog.Monitor {
 
     // Static direct instance pointer for the tightly-coupled idle service to use
     static StorageManagerService sSelf = null;
@@ -206,18 +202,12 @@
         }
     }
 
-    /** Flag to enable binder-based interface to vold */
-    private static final boolean ENABLE_BINDER = true;
-
     private static final boolean DEBUG_EVENTS = false;
     private static final boolean DEBUG_OBB = false;
 
     // Disable this since it messes up long-running cryptfs operations.
     private static final boolean WATCHDOG_ENABLE = false;
 
-    /** Flag to enable ASECs */
-    private static final boolean ASEC_ENABLE = false;
-
     /**
      * Our goal is for all Android devices to be usable as development devices,
      * which includes the new Direct Boot mode added in N. For devices that
@@ -232,66 +222,9 @@
     private static final String TAG_STORAGE_BENCHMARK = "storage_benchmark";
     private static final String TAG_STORAGE_TRIM = "storage_trim";
 
-    private static final String VOLD_TAG = "VoldConnector";
-    private static final String CRYPTD_TAG = "CryptdConnector";
-
-    /** Maximum number of ASEC containers allowed to be mounted. */
-    private static final int MAX_CONTAINERS = 250;
-
     /** Magic value sent by MoveTask.cpp */
     private static final int MOVE_STATUS_COPY_FINISHED = 82;
 
-    /*
-     * Internal vold response code constants
-     */
-    class VoldResponseCode {
-        /*
-         * 100 series - Requestion action was initiated; expect another reply
-         *              before proceeding with a new command.
-         */
-        public static final int VolumeListResult               = 110;
-        public static final int AsecListResult                 = 111;
-        public static final int StorageUsersListResult         = 112;
-        public static final int CryptfsGetfieldResult          = 113;
-
-        /*
-         * 200 series - Requestion action has been successfully completed.
-         */
-        public static final int ShareStatusResult              = 210;
-        public static final int AsecPathResult                 = 211;
-        public static final int ShareEnabledResult             = 212;
-
-        /*
-         * 400 series - Command was accepted, but the requested action
-         *              did not take place.
-         */
-        public static final int OpFailedNoMedia                = 401;
-        public static final int OpFailedMediaBlank             = 402;
-        public static final int OpFailedMediaCorrupt           = 403;
-        public static final int OpFailedVolNotMounted          = 404;
-        public static final int OpFailedStorageBusy            = 405;
-        public static final int OpFailedStorageNotFound        = 406;
-
-        /*
-         * 600 series - Unsolicited broadcasts.
-         */
-        public static final int DISK_CREATED = 640;
-        public static final int DISK_SIZE_CHANGED = 641;
-        public static final int DISK_LABEL_CHANGED = 642;
-        public static final int DISK_SCANNED = 643;
-        public static final int DISK_SYS_PATH_CHANGED = 644;
-        public static final int DISK_DESTROYED = 649;
-
-        public static final int VOLUME_CREATED = 650;
-        public static final int VOLUME_STATE_CHANGED = 651;
-        public static final int VOLUME_FS_TYPE_CHANGED = 652;
-        public static final int VOLUME_FS_UUID_CHANGED = 653;
-        public static final int VOLUME_FS_LABEL_CHANGED = 654;
-        public static final int VOLUME_PATH_CHANGED = 655;
-        public static final int VOLUME_INTERNAL_PATH_CHANGED = 656;
-        public static final int VOLUME_DESTROYED = 659;
-    }
-
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADD_PRIMARY = 2;
     private static final int VERSION_FIX_PRIMARY = 3;
@@ -453,17 +386,6 @@
         }
     }
 
-    private static String escapeNull(String arg) {
-        if (TextUtils.isEmpty(arg)) {
-            return "!";
-        } else {
-            if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
-                throw new IllegalArgumentException(arg);
-            }
-            return arg;
-        }
-    }
-
     /** List of crypto types.
       * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
       * corresponding commands in CommandListener.cpp */
@@ -472,12 +394,6 @@
 
     private final Context mContext;
 
-    private final NativeDaemonConnector mConnector;
-    private final NativeDaemonConnector mCryptConnector;
-
-    private final Thread mConnectorThread;
-    private final Thread mCryptConnectorThread;
-
     private volatile IVold mVold;
 
     private volatile boolean mSystemReady = false;
@@ -489,20 +405,6 @@
     private final Callbacks mCallbacks;
     private final LockPatternUtils mLockPatternUtils;
 
-    // Two connectors - mConnector & mCryptConnector
-    private final CountDownLatch mConnectedSignal = new CountDownLatch(2);
-    private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
-
-    private final Object mUnmountLock = new Object();
-    @GuardedBy("mUnmountLock")
-    private CountDownLatch mUnmountSignal;
-
-    /**
-     * Private hash of currently mounted secure containers.
-     * Used as a lock in methods to manipulate secure containers.
-     */
-    final private HashSet<String> mAsecMountSet = new HashSet<String>();
-
     /**
      * The size of the crypto algorithm key in bits for OBB files. Currently
      * Twofish is used which takes 128-bit keys.
@@ -616,7 +518,7 @@
             if (DEBUG_OBB)
                 Slog.i(TAG, "onServiceDisconnected");
         }
-    };
+    }
 
     // Used in the ObbActionHandler
     private IMediaContainerService mContainerService = null;
@@ -655,13 +557,6 @@
                     break;
                 }
                 case H_FSTRIM: {
-                    if (!isReady()) {
-                        Slog.i(TAG, "fstrim requested, but no daemon connection yet; trying again");
-                        sendMessageDelayed(obtainMessage(H_FSTRIM, msg.obj),
-                                DateUtils.SECOND_IN_MILLIS);
-                        break;
-                    }
-
                     Slog.i(TAG, "Running fstrim idle maintenance");
 
                     // Remember when we kicked it off
@@ -687,12 +582,8 @@
                     final IStorageShutdownObserver obs = (IStorageShutdownObserver) msg.obj;
                     boolean success = false;
                     try {
-                        if (ENABLE_BINDER) {
-                            mVold.shutdown();
-                            success = true;
-                        } else {
-                            success = mConnector.execute("volume", "shutdown").isClassOk();
-                        }
+                        mVold.shutdown();
+                        success = true;
                     } catch (Exception e) {
                         Slog.wtf(TAG, e);
                     }
@@ -711,12 +602,7 @@
                         break;
                     }
                     try {
-                        if (ENABLE_BINDER) {
-                            mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
-                        } else {
-                            mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
-                                    vol.mountUserId);
-                        }
+                        mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
                     } catch (Exception e) {
                         Slog.wtf(TAG, e);
                     }
@@ -778,11 +664,7 @@
                 if (Intent.ACTION_USER_ADDED.equals(action)) {
                     final UserManager um = mContext.getSystemService(UserManager.class);
                     final int userSerialNumber = um.getUserSerialNumber(userId);
-                    if (ENABLE_BINDER) {
-                        mVold.onUserAdded(userId, userSerialNumber);
-                    } else {
-                        mConnector.execute("volume", "user_added", userId, userSerialNumber);
-                    }
+                    mVold.onUserAdded(userId, userSerialNumber);
                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                     synchronized (mVolumes) {
                         final int size = mVolumes.size();
@@ -794,11 +676,7 @@
                             }
                         }
                     }
-                    if (ENABLE_BINDER) {
-                        mVold.onUserRemoved(userId);
-                    } else {
-                        mConnector.execute("volume", "user_removed", userId);
-                    }
+                    mVold.onUserRemoved(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -806,22 +684,6 @@
         }
     };
 
-    @Override
-    public void waitForAsecScan() {
-        waitForLatch(mAsecsScanned, "mAsecsScanned");
-    }
-
-    private void waitForReady() {
-        waitForLatch(mConnectedSignal, "mConnectedSignal");
-    }
-
-    private void waitForLatch(CountDownLatch latch, String condition) {
-        try {
-            waitForLatch(latch, condition, -1);
-        } catch (TimeoutException ignored) {
-        }
-    }
-
     private void waitForLatch(CountDownLatch latch, String condition, long timeoutMillis)
             throws TimeoutException {
         final long startMillis = SystemClock.elapsedRealtime();
@@ -843,14 +705,6 @@
         }
     }
 
-    private boolean isReady() {
-        try {
-            return mConnectedSignal.await(0, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            return false;
-        }
-    }
-
     private void handleSystemReady() {
         initIfReadyAndConnected();
         resetIfReadyAndConnected();
@@ -917,19 +771,10 @@
             for (UserInfo user : users) {
                 try {
                     if (initLocked) {
-                        if (ENABLE_BINDER) {
-                            mVold.lockUserKey(user.id);
-                        } else {
-                            mCryptConnector.execute("cryptfs", "lock_user_key", user.id);
-                        }
+                        mVold.lockUserKey(user.id);
                     } else {
-                        if (ENABLE_BINDER) {
-                            mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
-                                    encodeBytes(null));
-                        } else {
-                            mCryptConnector.execute("cryptfs", "unlock_user_key", user.id,
-                                    user.serialNumber, "!", "!");
-                        }
+                        mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
+                                encodeBytes(null));
                     }
                 } catch (Exception e) {
                     Slog.wtf(TAG, e);
@@ -956,26 +801,14 @@
             }
 
             try {
-                if (ENABLE_BINDER) {
-                    mVold.reset();
-                } else {
-                    mConnector.execute("volume", "reset");
-                }
+                mVold.reset();
 
                 // Tell vold about all existing and started users
                 for (UserInfo user : users) {
-                    if (ENABLE_BINDER) {
-                        mVold.onUserAdded(user.id, user.serialNumber);
-                    } else {
-                        mConnector.execute("volume", "user_added", user.id, user.serialNumber);
-                    }
+                    mVold.onUserAdded(user.id, user.serialNumber);
                 }
                 for (int userId : systemUnlockedUsers) {
-                    if (ENABLE_BINDER) {
-                        mVold.onUserStarted(userId);
-                    } else {
-                        mConnector.execute("volume", "user_started", userId);
-                    }
+                    mVold.onUserStarted(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -990,11 +823,7 @@
         // staging area is ready so it's ready for zygote-forked apps to
         // bind mount against.
         try {
-            if (ENABLE_BINDER) {
-                mVold.onUserStarted(userId);
-            } else {
-                mConnector.execute("volume", "user_started", userId);
-            }
+            mVold.onUserStarted(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1020,11 +849,7 @@
         Slog.d(TAG, "onCleanupUser " + userId);
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.onUserStopped(userId);
-            } else {
-                mConnector.execute("volume", "user_stopped", userId);
-            }
+            mVold.onUserStopped(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1050,10 +875,6 @@
         return mLastMaintenance;
     }
 
-    /**
-     * Callback from NativeDaemonConnector
-     */
-    @Override
     public void onDaemonConnected() {
         mDaemonConnected = true;
         mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
@@ -1063,29 +884,11 @@
         initIfReadyAndConnected();
         resetIfReadyAndConnected();
 
-        /*
-         * Now that we've done our initialization, release
-         * the hounds!
-         */
-        mConnectedSignal.countDown();
-        if (mConnectedSignal.getCount() != 0) {
-            // More daemons need to connect
-            return;
-        }
-
         // On an encrypted device we can't see system properties yet, so pull
         // the system locale out of the mount service.
         if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
             copyLocaleFromMountService();
         }
-
-        // Let package manager load internal ASECs.
-        if (ASEC_ENABLE) {
-            mPms.scanAvailableAsecs();
-        }
-
-        // Notify people waiting for ASECs to be scanned that it's done.
-        mAsecsScanned.countDown();
     }
 
     private void copyLocaleFromMountService() {
@@ -1114,148 +917,6 @@
         SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
     }
 
-    /**
-     * Callback from NativeDaemonConnector
-     */
-    @Override
-    public boolean onCheckHoldWakeLock(int code) {
-        return false;
-    }
-
-    /**
-     * Callback from NativeDaemonConnector
-     */
-    @Override
-    public boolean onEvent(int code, String raw, String[] cooked) {
-        synchronized (mLock) {
-            try {
-                return onEventLocked(code, raw, cooked);
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        }
-    }
-
-    private boolean onEventLocked(int code, String raw, String[] cooked) throws RemoteException {
-        switch (code) {
-            case VoldResponseCode.DISK_CREATED: {
-                if (cooked.length != 3) break;
-                final String diskId = cooked[1];
-                final int flags = Integer.parseInt(cooked[2]);
-                mListener.onDiskCreated(diskId, flags);
-                break;
-            }
-            case VoldResponseCode.DISK_SIZE_CHANGED: {
-                if (cooked.length != 3) break;
-                final DiskInfo disk = mDisks.get(cooked[1]);
-                if (disk != null) {
-                    disk.size = Long.parseLong(cooked[2]);
-                }
-                break;
-            }
-            case VoldResponseCode.DISK_LABEL_CHANGED: {
-                final DiskInfo disk = mDisks.get(cooked[1]);
-                if (disk != null) {
-                    final StringBuilder builder = new StringBuilder();
-                    for (int i = 2; i < cooked.length; i++) {
-                        builder.append(cooked[i]).append(' ');
-                    }
-                    disk.label = builder.toString().trim();
-                }
-                break;
-            }
-            case VoldResponseCode.DISK_SCANNED: {
-                if (cooked.length != 2) break;
-                final String diskId = cooked[1];
-                mListener.onDiskScanned(diskId);
-                break;
-            }
-            case VoldResponseCode.DISK_SYS_PATH_CHANGED: {
-                if (cooked.length != 3) break;
-                final DiskInfo disk = mDisks.get(cooked[1]);
-                if (disk != null) {
-                    disk.sysPath = cooked[2];
-                }
-                break;
-            }
-            case VoldResponseCode.DISK_DESTROYED: {
-                if (cooked.length != 2) break;
-                final String diskId = cooked[1];
-                mListener.onDiskDestroyed(diskId);
-                break;
-            }
-
-            case VoldResponseCode.VOLUME_CREATED: {
-                final String volId = cooked[1];
-                final int type = Integer.parseInt(cooked[2]);
-                final String diskId = TextUtils.nullIfEmpty(cooked[3]);
-                final String partGuid = TextUtils.nullIfEmpty(cooked[4]);
-                mListener.onVolumeCreated(volId, type, diskId, partGuid);
-                break;
-            }
-            case VoldResponseCode.VOLUME_STATE_CHANGED: {
-                if (cooked.length != 3) break;
-                final String volId = cooked[1];
-                final int state = Integer.parseInt(cooked[2]);
-                mListener.onVolumeStateChanged(volId, state);
-                break;
-            }
-            case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
-                if (cooked.length != 3) break;
-                final VolumeInfo vol = mVolumes.get(cooked[1]);
-                if (vol != null) {
-                    vol.fsType = cooked[2];
-                }
-                break;
-            }
-            case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
-                if (cooked.length != 3) break;
-                final VolumeInfo vol = mVolumes.get(cooked[1]);
-                if (vol != null) {
-                    vol.fsUuid = cooked[2];
-                }
-                break;
-            }
-            case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
-                final VolumeInfo vol = mVolumes.get(cooked[1]);
-                if (vol != null) {
-                    final StringBuilder builder = new StringBuilder();
-                    for (int i = 2; i < cooked.length; i++) {
-                        builder.append(cooked[i]).append(' ');
-                    }
-                    vol.fsLabel = builder.toString().trim();
-                }
-                // TODO: notify listeners that label changed
-                break;
-            }
-            case VoldResponseCode.VOLUME_PATH_CHANGED: {
-                if (cooked.length != 3) break;
-                final String volId = cooked[1];
-                final String path = cooked[2];
-                mListener.onVolumePathChanged(volId, path);
-                break;
-            }
-            case VoldResponseCode.VOLUME_INTERNAL_PATH_CHANGED: {
-                if (cooked.length != 3) break;
-                final String volId = cooked[1];
-                final String internalPath = cooked[2];
-                mListener.onVolumeInternalPathChanged(volId, internalPath);
-                break;
-            }
-            case VoldResponseCode.VOLUME_DESTROYED: {
-                if (cooked.length != 2) break;
-                final String volId = cooked[1];
-                mListener.onVolumeDestroyed(volId);
-                break;
-            }
-            default: {
-                Slog.d(TAG, "Unhandled vold event " + code);
-            }
-        }
-
-        return true;
-    }
-
     private final IVoldListener mListener = new IVoldListener.Stub() {
         @Override
         public void onDiskCreated(String diskId, int flags) {
@@ -1654,24 +1315,6 @@
 
         LocalServices.addService(StorageManagerInternal.class, mStorageManagerInternal);
 
-        /*
-         * Create the connection to vold with a maximum queue of twice the
-         * amount of containers we'd ever expect to have. This keeps an
-         * "asec list" from blocking a thread repeatedly.
-         */
-
-        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
-                null);
-        mConnector.setDebug(true);
-        mConnector.setWarnIfHeld(mLock);
-        mConnectorThread = new Thread(mConnector, VOLD_TAG);
-
-        // Reuse parameters from first connector since they are tested and safe
-        mCryptConnector = new NativeDaemonConnector(this, "cryptd",
-                MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null);
-        mCryptConnector.setDebug(true);
-        mCryptConnectorThread = new Thread(mCryptConnector, CRYPTD_TAG);
-
         final IntentFilter userFilter = new IntentFilter();
         userFilter.addAction(Intent.ACTION_USER_ADDED);
         userFilter.addAction(Intent.ACTION_USER_REMOVED);
@@ -1689,8 +1332,6 @@
 
     private void start() {
         connect();
-        mConnectorThread.start();
-        mCryptConnectorThread.start();
     }
 
     private void connect() {
@@ -1713,6 +1354,7 @@
             mVold = IVold.Stub.asInterface(binder);
             try {
                 mVold.setListener(mListener);
+                onDaemonConnected();
                 return;
             } catch (RemoteException e) {
                 Slog.w(TAG, "vold listener rejected; trying again", e);
@@ -1864,62 +1506,15 @@
     }
 
     @Override
-    public boolean isUsbMassStorageConnected() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setUsbMassStorageEnabled(boolean enable) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isUsbMassStorageEnabled() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getVolumeState(String mountPoint) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isExternalStorageEmulated() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int mountVolume(String path) {
-        mount(findVolumeIdForPathOrThrow(path));
-        return 0;
-    }
-
-    @Override
-    public void unmountVolume(String path, boolean force, boolean removeEncryption) {
-        unmount(findVolumeIdForPathOrThrow(path));
-    }
-
-    @Override
-    public int formatVolume(String path) {
-        format(findVolumeIdForPathOrThrow(path));
-        return 0;
-    }
-
-    @Override
     public void mount(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         if (isMountDisallowed(vol)) {
             throw new SecurityException("Mounting " + volId + " restricted by policy");
         }
         try {
-            if (ENABLE_BINDER) {
-                mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
-            } else {
-                mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
-            }
+            mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1928,31 +1523,10 @@
     @Override
     public void unmount(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
-
-        // TODO: expand PMS to know about multiple volumes
-        if (vol.isPrimaryPhysical()) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                synchronized (mUnmountLock) {
-                    mUnmountSignal = new CountDownLatch(1);
-                    mPms.updateExternalMediaStatus(false, true);
-                    waitForLatch(mUnmountSignal, "mUnmountSignal");
-                    mUnmountSignal = null;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
         try {
-            if (ENABLE_BINDER) {
-                mVold.unmount(vol.id);
-            } else {
-                mConnector.execute("volume", "unmount", vol.id);
-            }
+            mVold.unmount(vol.id);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1961,15 +1535,10 @@
     @Override
     public void format(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.format(vol.id, "auto");
-            } else {
-                mConnector.execute("volume", "format", vol.id, "auto");
-            }
+            mVold.format(vol.id, "auto");
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1978,7 +1547,6 @@
     @Override
     public long benchmark(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
 
         // TODO: refactor for callers to provide a listener
         try {
@@ -2022,15 +1590,10 @@
     @Override
     public void partitionPublic(String diskId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
-            } else {
-                mConnector.execute("volume", "partition", diskId, "public");
-            }
+            mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
             waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -2041,15 +1604,10 @@
     public void partitionPrivate(String diskId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         enforceAdminUser();
-        waitForReady();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
-            } else {
-                mConnector.execute("volume", "partition", diskId, "private");
-            }
+            mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
             waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -2060,15 +1618,10 @@
     public void partitionMixed(String diskId, int ratio) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         enforceAdminUser();
-        waitForReady();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
-            } else {
-                mConnector.execute("volume", "partition", diskId, "mixed", ratio);
-            }
+            mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
             waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -2078,7 +1631,6 @@
     @Override
     public void setVolumeNickname(String fsUuid, String nickname) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         Preconditions.checkNotNull(fsUuid);
         synchronized (mLock) {
@@ -2092,7 +1644,6 @@
     @Override
     public void setVolumeUserFlags(String fsUuid, int flags, int mask) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         Preconditions.checkNotNull(fsUuid);
         synchronized (mLock) {
@@ -2106,7 +1657,6 @@
     @Override
     public void forgetVolume(String fsUuid) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         Preconditions.checkNotNull(fsUuid);
 
@@ -2131,7 +1681,6 @@
     @Override
     public void forgetAllVolumes() {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         synchronized (mLock) {
             for (int i = 0; i < mRecords.size(); i++) {
@@ -2155,11 +1704,7 @@
 
     private void forgetPartition(String partGuid) {
         try {
-            if (ENABLE_BINDER) {
-                mVold.forgetPartition(partGuid);
-            } else {
-                mConnector.execute("volume", "forget_partition", partGuid);
-            }
+            mVold.forgetPartition(partGuid);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -2168,14 +1713,6 @@
     @Override
     public void fstrim(int flags) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
-
-        String cmd;
-        if ((flags & StorageManager.FSTRIM_FLAG_DEEP) != 0) {
-            cmd = "dodtrim";
-        } else {
-            cmd = "dotrim";
-        }
 
         try {
             mVold.fstrim(flags, new IVoldTaskListener.Stub() {
@@ -2212,27 +1749,8 @@
     }
 
     private void remountUidExternalStorage(int uid, int mode) {
-        waitForReady();
-
-        String modeName = "none";
-        switch (mode) {
-            case Zygote.MOUNT_EXTERNAL_DEFAULT: {
-                modeName = "default";
-            } break;
-            case Zygote.MOUNT_EXTERNAL_READ: {
-                modeName = "read";
-            } break;
-            case Zygote.MOUNT_EXTERNAL_WRITE: {
-                modeName = "write";
-            } break;
-        }
-
         try {
-            if (ENABLE_BINDER) {
-                mVold.remountUid(uid, mode);
-            } else {
-                mConnector.execute("volume", "remount_uid", uid, modeName);
-            }
+            mVold.remountUid(uid, mode);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -2241,7 +1759,6 @@
     @Override
     public void setDebugFlags(int flags, int mask) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
             if (!EMULATE_FBE_SUPPORTED) {
@@ -2331,7 +1848,6 @@
     @Override
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo from;
         final VolumeInfo to;
@@ -2403,33 +1919,6 @@
         }
     }
 
-    @Override
-    public int[] getStorageUsers(String path) {
-        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
-        try {
-            final String[] r = NativeDaemonEvent.filterMessageList(
-                    mConnector.executeForList("storage", "users", path),
-                    VoldResponseCode.StorageUsersListResult);
-
-            // FMT: <pid> <process name>
-            int[] data = new int[r.length];
-            for (int i = 0; i < r.length; i++) {
-                String[] tok = r[i].split(" ");
-                try {
-                    data[i] = Integer.parseInt(tok[0]);
-                } catch (NumberFormatException nfe) {
-                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
-                    return new int[0];
-                }
-            }
-            return data;
-        } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to retrieve storage users list", e);
-            return new int[0];
-        }
-    }
-
     private void warnOnNotMounted() {
         synchronized (mLock) {
             for (int i = 0; i < mVolumes.size(); i++) {
@@ -2444,304 +1933,6 @@
         Slog.w(TAG, "No primary storage mounted!");
     }
 
-    public String[] getSecureContainerList() {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        try {
-            return NativeDaemonEvent.filterMessageList(
-                    mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
-        } catch (NativeDaemonConnectorException e) {
-            return new String[0];
-        }
-    }
-
-    public int createSecureContainer(String id, int sizeMb, String fstype, String key,
-            int ownerUid, boolean external) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        waitForReady();
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
-                    ownerUid, external ? "1" : "0");
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                mAsecMountSet.add(id);
-            }
-        }
-        return rc;
-    }
-
-    @Override
-    public int resizeSecureContainer(String id, int sizeMb, String key) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        waitForReady();
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "resize", id, sizeMb, new SensitiveArg(key));
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-        return rc;
-    }
-
-    public int finalizeSecureContainer(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "finalize", id);
-            /*
-             * Finalization does a remount, so no need
-             * to update mAsecMountSet
-             */
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-        return rc;
-    }
-
-    public int fixPermissionsSecureContainer(String id, int gid, String filename) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "fixperms", id, gid, filename);
-            /*
-             * Fix permissions does a remount, so no need to update
-             * mAsecMountSet
-             */
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-        return rc;
-    }
-
-    public int destroySecureContainer(String id, boolean force) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_DESTROY);
-        waitForReady();
-        warnOnNotMounted();
-
-        /*
-         * Force a GC to make sure AssetManagers in other threads of the
-         * system_server are cleaned up. We have to do this since AssetManager
-         * instances are kept as a WeakReference and it's possible we have files
-         * open on the external storage.
-         */
-        Runtime.getRuntime().gc();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            final Command cmd = new Command("asec", "destroy", id);
-            if (force) {
-                cmd.appendArg("force");
-            }
-            mConnector.execute(cmd);
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedStorageBusy;
-            } else {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                if (mAsecMountSet.contains(id)) {
-                    mAsecMountSet.remove(id);
-                }
-            }
-        }
-
-        return rc;
-    }
-
-    public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            if (mAsecMountSet.contains(id)) {
-                return StorageResultCode.OperationFailedStorageMounted;
-            }
-        }
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid,
-                    readOnly ? "ro" : "rw");
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code != VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                mAsecMountSet.add(id);
-            }
-        }
-        return rc;
-    }
-
-    public int unmountSecureContainer(String id, boolean force) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            if (!mAsecMountSet.contains(id)) {
-                return StorageResultCode.OperationFailedStorageNotMounted;
-            }
-         }
-
-        /*
-         * Force a GC to make sure AssetManagers in other threads of the
-         * system_server are cleaned up. We have to do this since AssetManager
-         * instances are kept as a WeakReference and it's possible we have files
-         * open on the external storage.
-         */
-        Runtime.getRuntime().gc();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            final Command cmd = new Command("asec", "unmount", id);
-            if (force) {
-                cmd.appendArg("force");
-            }
-            mConnector.execute(cmd);
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedStorageBusy;
-            } else {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                mAsecMountSet.remove(id);
-            }
-        }
-        return rc;
-    }
-
-    public boolean isSecureContainerMounted(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            return mAsecMountSet.contains(id);
-        }
-    }
-
-    public int renameSecureContainer(String oldId, String newId) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_RENAME);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            /*
-             * Because a mounted container has active internal state which cannot be
-             * changed while active, we must ensure both ids are not currently mounted.
-             */
-            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
-                return StorageResultCode.OperationFailedStorageMounted;
-            }
-        }
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "rename", oldId, newId);
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-
-        return rc;
-    }
-
-    public String getSecureContainerPath(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("asec", "path", id);
-            event.checkCode(VoldResponseCode.AsecPathResult);
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                Slog.i(TAG, String.format("Container '%s' not found", id));
-                return null;
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
-    }
-
-    public String getSecureContainerFilesystemPath(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("asec", "fspath", id);
-            event.checkCode(VoldResponseCode.AsecPathResult);
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                Slog.i(TAG, String.format("Container '%s' not found", id));
-                return null;
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
-    }
-
-    @Override
-    public void finishMediaUpdate() {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("no permission to call finishMediaUpdate()");
-        }
-        if (mUnmountSignal != null) {
-            mUnmountSignal.countDown();
-        } else {
-            Slog.w(TAG, "Odd, nobody asked to unmount?");
-        }
-    }
-
     private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
         if (callerUid == android.os.Process.SYSTEM_UID) {
             return true;
@@ -2762,10 +1953,10 @@
         return callerUid == packageUid;
     }
 
+    @Override
     public String getMountedObbPath(String rawPath) {
         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
 
-        waitForReady();
         warnOnNotMounted();
 
         final ObbState state;
@@ -2777,23 +1968,7 @@
             return null;
         }
 
-        if (ENABLE_BINDER) {
-            return findVolumeByIdOrThrow(state.volId).getPath().getAbsolutePath();
-        }
-
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("obb", "path", state.canonicalPath);
-            event.checkCode(VoldResponseCode.AsecPathResult);
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                return null;
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
+        return findVolumeByIdOrThrow(state.volId).getPath().getAbsolutePath();
     }
 
     @Override
@@ -2850,28 +2025,10 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeComplete();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "cryptocomplete");
-            return Integer.parseInt(event.getMessage());
-        } catch (NumberFormatException e) {
-            // Bad result - unexpected.
-            Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
-            return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
-        } catch (NativeDaemonConnectorException e) {
-            // Something bad happened.
-            Slog.w(TAG, "Error in communicating with cryptfs in validating");
+            return mVold.fdeComplete();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
             return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
         }
     }
@@ -2881,8 +2038,6 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "no permission to access the crypt keeper");
 
-        waitForReady();
-
         if (TextUtils.isEmpty(password)) {
             throw new IllegalArgumentException("password cannot be empty");
         }
@@ -2891,55 +2046,27 @@
             Slog.i(TAG, "decrypting storage...");
         }
 
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeCheckPassword(password);
-                mHandler.postDelayed(() -> {
-                    try {
-                        mVold.fdeRestart();
-                    } catch (Exception e) {
-                        Slog.wtf(TAG, e);
-                    }
-                }, DateUtils.SECOND_IN_MILLIS);
-                return 0;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
-
-            final int code = Integer.parseInt(event.getMessage());
-            if (code == 0) {
-                // Decrypt was successful. Post a delayed message before restarting in order
-                // to let the UI to clear itself
-                mHandler.postDelayed(new Runnable() {
-                    public void run() {
-                        try {
-                            mCryptConnector.execute("cryptfs", "restart");
-                        } catch (NativeDaemonConnectorException e) {
-                            Slog.e(TAG, "problem executing in background", e);
-                        }
-                    }
-                }, 1000); // 1 second
-            }
-
-            return code;
-        } catch (NativeDaemonConnectorException e) {
-            // Decryption failed
-            return e.getCode();
+            mVold.fdeCheckPassword(password);
+            mHandler.postDelayed(() -> {
+                try {
+                    mVold.fdeRestart();
+                } catch (Exception e) {
+                    Slog.wtf(TAG, e);
+                }
+            }, DateUtils.SECOND_IN_MILLIS);
+            return 0;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
         }
     }
 
+    @Override
     public int encryptStorage(int type, String password) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
         if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
             password = "";
         } else if (TextUtils.isEmpty(password)) {
@@ -2951,17 +2078,7 @@
         }
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE);
-            } else {
-                if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
-                    mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
-                            CRYPTO_TYPES[type]);
-                } else {
-                    mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
-                            CRYPTO_TYPES[type], new SensitiveArg(password));
-                }
-            }
+            mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
             return -1;
@@ -2974,12 +2091,11 @@
      *  @param type One of the CRYPTO_TYPE_XXX consts defined in StorageManager.
      *  @param password The password to set.
      */
+    @Override
     public int changeEncryptionPassword(int type, String password) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
         if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
             password = "";
         } else if (TextUtils.isEmpty(password)) {
@@ -2990,23 +2106,12 @@
             Slog.i(TAG, "changing encryption password...");
         }
 
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeChangePassword(type, password);
-                return 0;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return -1;
-            }
-        }
-
         try {
-            NativeDaemonEvent event = mCryptConnector.execute("cryptfs", "changepw", CRYPTO_TYPES[type],
-                        new SensitiveArg(password));
-            return Integer.parseInt(event.getMessage());
-        } catch (NativeDaemonConnectorException e) {
-            // Encryption failed
-            return e.getCode();
+            mVold.fdeChangePassword(type, password);
+            return 0;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return -1;
         }
     }
 
@@ -3027,30 +2132,16 @@
             throw new IllegalArgumentException("password cannot be empty");
         }
 
-        waitForReady();
-
         if (DEBUG_EVENTS) {
             Slog.i(TAG, "validating encryption password...");
         }
 
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeVerifyPassword(password);
-                return 0;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return -1;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
-            Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
-            return Integer.parseInt(event.getMessage());
-        } catch (NativeDaemonConnectorException e) {
-            // Encryption failed
-            return e.getCode();
+            mVold.fdeVerifyPassword(password);
+            return 0;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return -1;
         }
     }
 
@@ -3063,28 +2154,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeGetPasswordType();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return -1;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "getpwtype");
-            for (int i = 0; i < CRYPTO_TYPES.length; ++i) {
-                if (CRYPTO_TYPES[i].equals(event.getMessage()))
-                    return i;
-            }
-
-            throw new IllegalStateException("unexpected return from cryptfs");
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            return mVold.fdeGetPasswordType();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return -1;
         }
     }
 
@@ -3098,23 +2172,12 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeSetField(field, contents);
-                return;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "setfield", field, contents);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            mVold.fdeSetField(field, contents);
+            return;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return;
         }
     }
 
@@ -3128,29 +2191,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeGetField(field);
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return null;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            final String[] contents = NativeDaemonEvent.filterMessageList(
-                    mCryptConnector.executeForList("cryptfs", "getfield", field),
-                    VoldResponseCode.CryptfsGetfieldResult);
-            String result = new String();
-            for (String content : contents) {
-                result += content;
-            }
-            return result;
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            return mVold.fdeGetField(field);
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return null;
         }
     }
 
@@ -3163,23 +2208,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.isConvertibleToFbe();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return false;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "isConvertibleToFBE");
-            return Integer.parseInt(event.getMessage()) != 0;
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            return mVold.isConvertibleToFbe();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return false;
         }
     }
 
@@ -3188,31 +2221,10 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "only keyguard can retrieve password");
 
-        if (!isReady()) {
-            return new String();
-        }
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeGetPassword();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return null;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "getpw");
-            if ("-1".equals(event.getMessage())) {
-                // -1 equals no password
-                return null;
-            }
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        } catch (IllegalArgumentException e) {
-            Slog.e(TAG, "Invalid response to getPassword");
+            return mVold.fdeGetPassword();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
             return null;
         }
     }
@@ -3222,40 +2234,21 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "only keyguard can clear password");
 
-        if (!isReady()) {
-            return;
-        }
-
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeClearPassword();
-                return;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "clearpw");
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            mVold.fdeClearPassword();
+            return;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return;
         }
     }
 
     @Override
     public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.createUserKey(userId, serialNumber, ephemeral);
-            } else {
-                mCryptConnector.execute("cryptfs", "create_user_key", userId, serialNumber,
-                        ephemeral ? 1 : 0);
-            }
+            mVold.createUserKey(userId, serialNumber, ephemeral);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3264,14 +2257,9 @@
     @Override
     public void destroyUserKey(int userId) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.destroyUserKey(userId);
-            } else {
-                mCryptConnector.execute("cryptfs", "destroy_user_key", userId);
-            }
+            mVold.destroyUserKey(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3295,16 +2283,9 @@
     @Override
     public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
-            } else {
-                mCryptConnector.execute("cryptfs", "add_user_key_auth", userId, serialNumber,
-                        new SensitiveArg(encodeBytes(token)),
-                        new SensitiveArg(encodeBytes(secret)));
-            }
+            mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3316,14 +2297,9 @@
     @Override
     public void fixateNewestUserKeyAuth(int userId) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.fixateNewestUserKeyAuth(userId);
-            } else {
-                mCryptConnector.execute("cryptfs", "fixate_newest_user_key_auth", userId);
-            }
+            mVold.fixateNewestUserKeyAuth(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3332,7 +2308,6 @@
     @Override
     public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         if (StorageManager.isFileEncryptedNativeOrEmulated()) {
             // When a user has secure lock screen, require secret to actually unlock.
@@ -3342,14 +2317,8 @@
             }
 
             try {
-                if (ENABLE_BINDER) {
-                    mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
-                            encodeBytes(secret));
-                } else {
-                    mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,
-                            new SensitiveArg(encodeBytes(token)),
-                            new SensitiveArg(encodeBytes(secret)));
-                }
+                mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
+                        encodeBytes(secret));
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
                 return;
@@ -3369,14 +2338,9 @@
     @Override
     public void lockUserKey(int userId) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.lockUserKey(userId);
-            } else {
-                mCryptConnector.execute("cryptfs", "lock_user_key", userId);
-            }
+            mVold.lockUserKey(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
             return;
@@ -3397,15 +2361,9 @@
     @Override
     public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
-            } else {
-                mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
-                        userId, serialNumber, flags);
-            }
+            mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3414,15 +2372,9 @@
     @Override
     public void destroyUserStorage(String volumeUuid, int userId, int flags) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.destroyUserStorage(volumeUuid, userId, flags);
-            } else {
-                mCryptConnector.execute("cryptfs", "destroy_user_storage", escapeNull(volumeUuid),
-                        userId, flags);
-            }
+            mVold.destroyUserStorage(volumeUuid, userId, flags);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3431,14 +2383,9 @@
     @Override
     public void secdiscard(String path) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.secdiscard(path);
-            } else {
-                mCryptConnector.execute("cryptfs", "secdiscard", escapeNull(path));
-            }
+            mVold.secdiscard(path);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3453,33 +2400,18 @@
 
         @Override
         public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
-            if (ENABLE_BINDER) {
-                try {
-                    return new ParcelFileDescriptor(
-                            mVold.mountAppFuse(uid, Process.myPid(), mountId));
-                } catch (Exception e) {
-                    throw new NativeDaemonConnectorException("Failed to mount", e);
-                }
-            } else {
-                final NativeDaemonEvent event = mConnector.execute(
-                        "appfuse", "mount", uid, Process.myPid(), mountId);
-                opened = true;
-                if (event.getFileDescriptors() == null ||
-                    event.getFileDescriptors().length == 0) {
-                    throw new NativeDaemonConnectorException("Cannot obtain device FD");
-                }
-                return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
+            try {
+                return new ParcelFileDescriptor(
+                        mVold.mountAppFuse(uid, Process.myPid(), mountId));
+            } catch (Exception e) {
+                throw new NativeDaemonConnectorException("Failed to mount", e);
             }
         }
 
         @Override
         public void close() throws Exception {
             if (opened) {
-                if (ENABLE_BINDER) {
-                    mVold.unmountAppFuse(uid, Process.myPid(), mountId);
-                } else {
-                    mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
-                }
+                mVold.unmountAppFuse(uid, Process.myPid(), mountId);
                 opened = false;
             }
         }
@@ -3568,11 +2500,7 @@
             }
 
             try {
-                if (ENABLE_BINDER) {
-                    mVold.mkdirs(appPath);
-                } else {
-                    mConnector.execute("volume", "mkdirs", appPath);
-                }
+                mVold.mkdirs(appPath);
                 return 0;
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -4124,7 +3052,6 @@
 
         @Override
         public void handleExecute() throws IOException, RemoteException {
-            waitForReady();
             warnOnNotMounted();
 
             final ObbInfo obbInfo = getObbInfo();
@@ -4174,19 +3101,9 @@
 
             int rc = StorageResultCode.OperationSucceeded;
             try {
-                if (ENABLE_BINDER) {
-                    mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
-                            mObbState.ownerGid);
-                    mVold.mount(mObbState.volId, 0, -1);
-                } else {
-                    mConnector.execute("obb", "mount", mObbState.canonicalPath,
-                            new SensitiveArg(hashedKey), mObbState.ownerGid);
-                }
-            } catch (NativeDaemonConnectorException e) {
-                int code = e.getCode();
-                if (code != VoldResponseCode.OpFailedStorageBusy) {
-                    rc = StorageResultCode.OperationFailedInternalError;
-                }
+                mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
+                        mObbState.ownerGid);
+                mVold.mount(mObbState.volId, 0, -1);
             } catch (Exception e) {
                 Slog.w(TAG, e);
                 rc = StorageResultCode.OperationFailedInternalError;
@@ -4233,7 +3150,6 @@
 
         @Override
         public void handleExecute() throws IOException {
-            waitForReady();
             warnOnNotMounted();
 
             final ObbState existingState;
@@ -4255,27 +3171,9 @@
 
             int rc = StorageResultCode.OperationSucceeded;
             try {
-                if (ENABLE_BINDER) {
-                    mVold.unmount(mObbState.volId);
-                    mVold.destroyObb(mObbState.volId);
-                    mObbState.volId = null;
-                } else {
-                    final Command cmd = new Command("obb", "unmount", mObbState.canonicalPath);
-                    if (mForceUnmount) {
-                        cmd.appendArg("force");
-                    }
-                    mConnector.execute(cmd);
-                }
-            } catch (NativeDaemonConnectorException e) {
-                int code = e.getCode();
-                if (code == VoldResponseCode.OpFailedStorageBusy) {
-                    rc = StorageResultCode.OperationFailedStorageBusy;
-                } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                    // If it's not mounted then we've already won.
-                    rc = StorageResultCode.OperationSucceeded;
-                } else {
-                    rc = StorageResultCode.OperationFailedInternalError;
-                }
+                mVold.unmount(mObbState.volId);
+                mVold.destroyObb(mObbState.volId);
+                mObbState.volId = null;
             } catch (Exception e) {
                 Slog.w(TAG, e);
                 rc = StorageResultCode.OperationFailedInternalError;
@@ -4506,18 +3404,6 @@
         }
 
         pw.println();
-        pw.println("mConnector:");
-        pw.increaseIndent();
-        mConnector.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println("mCryptConnector:");
-        pw.increaseIndent();
-        mCryptConnector.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
         pw.print("Last maintenance: ");
         pw.println(TimeUtils.formatForLogging(mLastMaintenance));
     }
@@ -4525,11 +3411,10 @@
     /** {@inheritDoc} */
     @Override
     public void monitor() {
-        if (mConnector != null) {
-            mConnector.monitor();
-        }
-        if (mCryptConnector != null) {
-            mCryptConnector.monitor();
+        try {
+            mVold.monitor();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
         }
     }
 
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 57271fa..92cbd3d 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -103,6 +103,7 @@
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.stats.StatsCompanionService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
@@ -151,7 +152,7 @@
      * them from the build system somehow.
      */
     private static final String BACKUP_MANAGER_SERVICE_CLASS =
-            "com.android.server.backup.BackupManagerService$Lifecycle";
+            "com.android.server.backup.RefactoredBackupManagerService$Lifecycle";
     private static final String APPWIDGET_SERVICE_CLASS =
             "com.android.server.appwidget.AppWidgetService";
     private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
@@ -667,9 +668,11 @@
         traceEnd();
 
         // Tracks whether the updatable WebView is in a ready state and watches for update installs.
-        traceBeginAndSlog("StartWebViewUpdateService");
-        mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
-        traceEnd();
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            traceBeginAndSlog("StartWebViewUpdateService");
+            mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
+            traceEnd();
+        }
     }
 
     /**
@@ -681,6 +684,7 @@
         VibratorService vibrator = null;
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
+        IpSecService ipSecService = null;
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
         ConnectivityService connectivity = null;
@@ -1030,6 +1034,15 @@
                     reportWtf("starting NetworkManagement Service", e);
                 }
                 traceEnd();
+
+                traceBeginAndSlog("StartIpSecService");
+                try {
+                    ipSecService = IpSecService.create(context);
+                    ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+                } catch (Throwable e) {
+                    reportWtf("starting IpSec Service", e);
+                }
+                traceEnd();
             }
 
             if (!disableNonCoreServices && !disableTextServices) {
@@ -1080,6 +1093,14 @@
                     traceBeginAndSlog("StartWifiRtt");
                     mSystemServiceManager.startService("com.android.server.wifi.RttService");
                     traceEnd();
+
+                    if (context.getPackageManager().hasSystemFeature(
+                            PackageManager.FEATURE_WIFI_RTT)) {
+                        traceBeginAndSlog("StartRttService");
+                        mSystemServiceManager.startService(
+                                "com.android.server.wifi.rtt.RttService");
+                        traceEnd();
+                    }
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1087,8 +1108,6 @@
                     traceBeginAndSlog("StartWifiAware");
                     mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
                     traceEnd();
-                } else {
-                    Slog.i(TAG, "No Wi-Fi Aware Service (Aware support Not Present)");
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1146,20 +1165,6 @@
                 traceEnd();
             }
 
-            /*
-             * StorageManagerService has a few dependencies: Notification Manager and
-             * AppWidget Provider. Make sure StorageManagerService is completely started
-             * first before continuing.
-             */
-            if (storageManager != null && !mOnlyCore) {
-                traceBeginAndSlog("WaitForAsecScan");
-                try {
-                    storageManager.waitForAsecScan();
-                } catch (RemoteException ignored) {
-                }
-                traceEnd();
-            }
-
             traceBeginAndSlog("StartNotificationManager");
             mSystemServiceManager.startService(NotificationManagerService.class);
             SystemNotificationChannels.createAll(context);
@@ -1209,18 +1214,6 @@
                 traceEnd();
             }
 
-            // timezone.RulesManagerService will prevent a device starting up if the chain of trust
-            // required for safe time zone updates might be broken. RuleManagerService cannot do
-            // this check when mOnlyCore == true, so we don't enable the service in this case.
-            final boolean startRulesManagerService =
-                    !mOnlyCore && context.getResources().getBoolean(
-                            R.bool.config_enableUpdateableTimeZoneRules);
-            if (startRulesManagerService) {
-                traceBeginAndSlog("StartTimeZoneRulesManagerService");
-                mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
-                traceEnd();
-            }
-
             traceBeginAndSlog("StartAudioService");
             mSystemServiceManager.startService(AudioService.Lifecycle.class);
             traceEnd();
@@ -1361,6 +1354,19 @@
             }
             traceEnd();
 
+            // timezone.RulesManagerService will prevent a device starting up if the chain of trust
+            // required for safe time zone updates might be broken. RuleManagerService cannot do
+            // this check when mOnlyCore == true, so we don't enable the service in this case.
+            // This service requires that JobSchedulerService is already started when it starts.
+            final boolean startRulesManagerService =
+                    !mOnlyCore && context.getResources().getBoolean(
+                            R.bool.config_enableUpdateableTimeZoneRules);
+            if (startRulesManagerService) {
+                traceBeginAndSlog("StartTimeZoneRulesManagerService");
+                mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
+                traceEnd();
+            }
+
             if (!disableNetwork && !disableNetworkTime) {
                 traceBeginAndSlog("StartNetworkTimeUpdateService");
                 try {
@@ -1536,6 +1542,11 @@
             traceEnd();
         }
 
+        // Statsd helper
+        traceBeginAndSlog("StartStatsCompanionService");
+        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+        traceEnd();
+
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
         final boolean safeMode = wm.detectSafeMode();
@@ -1661,6 +1672,7 @@
         final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
         final MediaRouterService mediaRouterF = mediaRouter;
         final MmsServiceBroker mmsServiceF = mmsService;
+        final IpSecService ipSecServiceF = ipSecService;
         final WindowManagerService windowManagerF = wm;
 
         // We now tell the activity manager it is okay to run third party
@@ -1683,10 +1695,10 @@
             traceEnd();
 
             // No dependency on Webview preparation in system server. But this should
-            // be completed before allowring 3rd party
+            // be completed before allowing 3rd party
             final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
             Future<?> webviewPrep = null;
-            if (!mOnlyCore) {
+            if (!mOnlyCore && mWebViewUpdateService != null) {
                 webviewPrep = SystemServerInitThreadPool.get().submit(() -> {
                     Slog.i(TAG, WEBVIEW_PREPARATION);
                     TimingsTraceLog traceLog = new TimingsTraceLog(
@@ -1731,6 +1743,13 @@
                         .networkScoreAndNetworkManagementServiceReady();
             }
             traceEnd();
+            traceBeginAndSlog("MakeIpSecServiceReady");
+            try {
+                if (ipSecServiceF != null) ipSecServiceF.systemReady();
+            } catch (Throwable e) {
+                reportWtf("making IpSec Service ready", e);
+            }
+            traceEnd();
             traceBeginAndSlog("MakeNetworkStatsServiceReady");
             try {
                 if (networkStatsF != null) networkStatsF.systemReady();
diff --git a/com/android/server/accounts/AccountManagerService.java b/com/android/server/accounts/AccountManagerService.java
index 8ae592f..4e15e5d 100644
--- a/com/android/server/accounts/AccountManagerService.java
+++ b/com/android/server/accounts/AccountManagerService.java
@@ -2969,9 +2969,13 @@
                              * have users launching arbitrary activities by tricking users to
                              * interact with malicious notifications.
                              */
-                            checkKeyIntent(
+                            if (!checkKeyIntent(
                                     Binder.getCallingUid(),
-                                    intent);
+                                    intent)) {
+                                onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                                        "invalid intent in bundle returned");
+                                return;
+                            }
                             doNotification(
                                     mAccounts,
                                     account,
@@ -3366,9 +3370,13 @@
             Intent intent = null;
             if (result != null
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
-                checkKeyIntent(
+                if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent);
+                        intent)) {
+                    onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "invalid intent in bundle returned");
+                    return;
+                }
             }
             IAccountManagerResponse response;
             if (mExpectActivityLaunch && result != null
@@ -4716,9 +4724,7 @@
          * into launching arbitrary intents on the device via by tricking to click authenticator
          * supplied entries in the system Settings app.
          */
-        protected void checkKeyIntent(
-                int authUid,
-                Intent intent) throws SecurityException {
+         protected boolean checkKeyIntent(int authUid, Intent intent) {
             intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
@@ -4727,6 +4733,9 @@
             try {
                 PackageManager pm = mContext.getPackageManager();
                 ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+                if (resolveInfo == null) {
+                    return false;
+                }
                 ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
                 int targetUid = targetActivityInfo.applicationInfo.uid;
                 if (!isExportedSystemActivity(targetActivityInfo)
@@ -4736,9 +4745,10 @@
                     String activityName = targetActivityInfo.name;
                     String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
                             + "does not share a signature with the supplying authenticator (%s).";
-                    throw new SecurityException(
-                            String.format(tmpl, activityName, pkgName, mAccountType));
+                    Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType));
+                    return false;
                 }
+                return true;
             } finally {
                 Binder.restoreCallingIdentity(bid);
             }
@@ -4888,9 +4898,13 @@
             }
             if (result != null
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
-                checkKeyIntent(
+                if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent);
+                        intent)) {
+                    onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "invalid intent in bundle returned");
+                    return;
+                }
             }
             if (result != null
                     && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
@@ -5285,7 +5299,7 @@
                         == PackageManager.PERMISSION_GRANTED) {
                     // Checks runtime permission revocation.
                     final int opCode = AppOpsManager.permissionToOpCode(perm);
-                    if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+                    if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
                             opCode, uid, packageName) == AppOpsManager.MODE_ALLOWED) {
                         return true;
                     }
@@ -5306,7 +5320,7 @@
                     Log.v(TAG, "  caller uid " + callingUid + " has " + perm);
                 }
                 final int opCode = AppOpsManager.permissionToOpCode(perm);
-                if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+                if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
                         opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) {
                     return true;
                 }
diff --git a/com/android/server/am/ActiveServices.java b/com/android/server/am/ActiveServices.java
index e0cde72..2131731 100644
--- a/com/android/server/am/ActiveServices.java
+++ b/com/android/server/am/ActiveServices.java
@@ -2162,6 +2162,15 @@
             }
         }
 
+        if (r.fgRequired) {
+            if (DEBUG_FOREGROUND_SERVICE) {
+                Slog.v(TAG, "Whitelisting " + UserHandle.formatUid(r.appInfo.uid)
+                        + " for fg-service launch");
+            }
+            mAm.tempWhitelistUidLocked(r.appInfo.uid,
+                    SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch");
+        }
+
         if (!mPendingServices.contains(r)) {
             mPendingServices.add(r);
         }
@@ -3141,7 +3150,7 @@
                         sr.userId, sr.crashCount, sr.shortName, app.pid);
                 bringDownServiceLocked(sr);
             } else if (!allowRestart
-                    || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) {
+                    || !mAm.mUserController.isUserRunning(sr.userId, 0)) {
                 bringDownServiceLocked(sr);
             } else {
                 boolean canceled = scheduleServiceRestartLocked(sr, true);
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
new file mode 100644
index 0000000..8bcbfbe
--- /dev/null
+++ b/com/android/server/am/ActivityDisplay.java
@@ -0,0 +1,414 @@
+/*
+ * 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 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_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+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.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+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.proto.ActivityDisplayProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
+import static com.android.server.am.proto.ActivityDisplayProto.ID;
+
+import android.app.ActivityManagerInternal;
+import android.app.WindowConfiguration;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.ConfigurationContainer;
+
+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 {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM;
+    private static final String TAG_STACK = TAG + POSTFIX_STACK;
+
+    static final int POSITION_TOP = Integer.MAX_VALUE;
+    static final int POSITION_BOTTOM = Integer.MIN_VALUE;
+
+    private ActivityStackSupervisor mSupervisor;
+    /** Actual Display this object tracks. */
+    int mDisplayId;
+    Display mDisplay;
+
+    /** 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<>();
+
+    /** Array of all UIDs that are present on the display. */
+    private IntArray mDisplayAccessUIDs = new IntArray();
+
+    /** All tokens used to put activities on this stack to sleep (including mOffToken) */
+    final ArrayList<ActivityManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
+    /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
+    ActivityManagerInternal.SleepToken mOffToken;
+
+    private boolean mSleeping;
+
+    ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
+        mSupervisor = supervisor;
+        mDisplayId = displayId;
+        final Display display = supervisor.mDisplayManager.getDisplay(displayId);
+        if (display == null) {
+            throw new IllegalStateException("Display does not exist displayId=" + displayId);
+        }
+        mDisplay = display;
+    }
+
+    void addChild(ActivityStack stack, int position) {
+        if (position == POSITION_BOTTOM) {
+            position = 0;
+        } else if (position == POSITION_TOP) {
+            position = mStacks.size();
+        }
+        if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack
+                + " to displayId=" + mDisplayId + " position=" + position);
+        positionChildAt(stack, position);
+        mSupervisor.mService.updateSleepIfNeededLocked();
+    }
+
+    void removeChild(ActivityStack stack) {
+        if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
+                + " from displayId=" + mDisplayId);
+        mStacks.remove(stack);
+        mSupervisor.mService.updateSleepIfNeededLocked();
+    }
+
+    void positionChildAtTop(ActivityStack stack) {
+        positionChildAt(stack, mStacks.size());
+    }
+
+    void positionChildAtBottom(ActivityStack stack) {
+        positionChildAt(stack, 0);
+    }
+
+    private void positionChildAt(ActivityStack stack, int position) {
+        mStacks.remove(stack);
+        mStacks.add(getTopInsertPosition(stack, position), stack);
+    }
+
+    private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
+        int position = mStacks.size();
+        if (position > 0) {
+            final ActivityStack topStack = mStacks.get(position - 1);
+            if (topStack.getWindowConfiguration().isAlwaysOnTop() && topStack != stack) {
+                // If the top stack is always on top, we move this stack just below it.
+                position--;
+            }
+        }
+        return Math.min(position, candidatePosition);
+    }
+
+    <T extends ActivityStack> T getStack(int stackId) {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mStacks.get(i);
+            if (stack.mStackId == stackId) {
+                return (T) stack;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the topmost stack on the display that is compatible with the input windowing mode and
+     * activity type. {@code null} means no compatible stack on the display.
+     * @see ConfigurationContainer#isCompatible(int, int)
+     */
+    <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+        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;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @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;
+        }
+        return createStack(windowingMode, activityType, onTop);
+    }
+
+    /**
+     * Creates a stack matching the input windowing mode and activity type on this display.
+     * @param windowingMode The windowing mode the stack should be created in. If
+     *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
+     *                      be created in {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+     * @param activityType The activityType the stack should be created in. If
+     *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
+     *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+     * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+     * @return The newly created stack.
+     */
+    <T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) {
+
+        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+            // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
+            // anything else should be passing it in anyways...
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+
+        if (activityType != ACTIVITY_TYPE_STANDARD) {
+            // For now there can be only one stack of a particular non-standard activity type on a
+            // display. So, get that ignoring whatever windowing mode it is currently in.
+            T stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
+            if (stack != null) {
+                throw new IllegalArgumentException("Stack=" + stack + " of activityType="
+                        + activityType + " already on display=" + this + ". Can't have multiple.");
+            }
+        }
+
+        final ActivityManagerService service = mSupervisor.mService;
+        if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+                service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
+                service.mSupportsPictureInPicture, activityType)) {
+            throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
+                    + windowingMode);
+        }
+
+        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;
+            }
+        }
+
+        if (stackId == INVALID_STACK_ID) {
+            stackId = mSupervisor.getNextStackId();
+        }
+
+        final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
+
+        if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            // Make sure recents stack exist when creating a dock stack as it normally need to be on
+            // the other side of the docked stack and we make visibility decisions based on that.
+            // TODO: Not sure if this is needed after we change to calculate visibility based on
+            // stack z-order vs. id.
+            getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
+        }
+
+        return stack;
+    }
+
+    @VisibleForTesting
+    <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
+            int stackId, boolean onTop) {
+        switch (windowingMode) {
+            case WINDOWING_MODE_PINNED:
+                return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
+            default:
+                return (T) new ActivityStack(
+                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
+        }
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        if (windowingModes == null || windowingModes.length == 0) {
+            return;
+        }
+
+        for (int j = windowingModes.length - 1 ; j >= 0; --j) {
+            final int windowingMode = windowingModes[j];
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack stack = mStacks.get(i);
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    continue;
+                }
+                if (stack.getWindowingMode() != windowingMode) {
+                    continue;
+                }
+                mSupervisor.removeStackLocked(stack.mStackId);
+            }
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        if (activityTypes == null || activityTypes.length == 0) {
+            return;
+        }
+
+        for (int j = activityTypes.length - 1 ; j >= 0; --j) {
+            final int activityType = activityTypes[j];
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack stack = mStacks.get(i);
+                if (stack.getActivityType() == activityType) {
+                    mSupervisor.removeStackLocked(stack.mStackId);
+                }
+            }
+        }
+    }
+
+    /** 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) {
+            final ActivityStack stack = mStacks.get(i);
+            if (stack.getWindowingMode() == excludeWindowingMode) {
+                continue;
+            }
+            if (stack.shouldBeVisible(null /* starting */)) {
+                return stack.getActivityType();
+            }
+        }
+        return ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    ActivityStack getSplitScreenStack() {
+        return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    boolean hasSplitScreenStack() {
+        return getSplitScreenStack() != null;
+    }
+
+    PinnedActivityStack getPinnedStack() {
+        return getStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    boolean hasPinnedStack() {
+        return getPinnedStack() != null;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
+    }
+
+    @Override
+    protected int getChildCount() {
+        return mStacks.size();
+    }
+
+    @Override
+    protected ConfigurationContainer getChildAt(int index) {
+        return mStacks.get(index);
+    }
+
+    @Override
+    protected ConfigurationContainer getParent() {
+        return mSupervisor;
+    }
+
+    boolean isPrivate() {
+        return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
+    }
+
+    boolean isUidPresent(int uid) {
+        for (ActivityStack stack : mStacks) {
+            if (stack.isUidPresent(uid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Update and get all UIDs that are present on the display and have access to it. */
+    IntArray getPresentUIDs() {
+        mDisplayAccessUIDs.clear();
+        for (ActivityStack stack : mStacks) {
+            stack.getPresentUIDs(mDisplayAccessUIDs);
+        }
+        return mDisplayAccessUIDs;
+    }
+
+    boolean shouldDestroyContentOnRemove() {
+        return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
+    }
+
+    boolean shouldSleep() {
+        return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
+                && (mSupervisor.mService.mRunningVoice == null);
+    }
+
+    boolean isSleeping() {
+        return mSleeping;
+    }
+
+    void setIsSleeping(boolean asleep) {
+        mSleeping = asleep;
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        proto.write(ID, mDisplayId);
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.writeToProto(proto, STACKS);
+        }
+        proto.end(token);
+    }
+}
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index 02eb3b4..e6fe620 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -33,6 +33,14 @@
 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;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -145,7 +153,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IMMERSIVE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -163,10 +170,8 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_URI_PERMISSION;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
 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.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_ONLY;
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
@@ -177,8 +182,8 @@
 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_ACTIVITY_RELAUNCH;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
@@ -259,8 +264,8 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
 import android.content.pm.PermissionInfo;
@@ -349,6 +354,7 @@
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -402,6 +408,7 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.utils.PriorityDump;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.PinnedStackWindowController;
 import com.android.server.wm.WindowManagerService;
@@ -682,11 +689,6 @@
     ActivityInfo mLastAddedTaskActivity;
 
     /**
-     * List of packages whitelisted by DevicePolicyManager for locktask. Indexed by userId.
-     */
-    SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
-
-    /**
      * The package name of the DeviceOwner. This package is not permitted to have its data cleared.
      */
     String mDeviceOwnerName;
@@ -712,9 +714,45 @@
     @VisibleForTesting
     long mWaitForNetworkTimeoutMs;
 
+    /**
+     * Helper class which parses out priority arguments and dumps sections according to their
+     * priority. If priority arguments are omitted, function calls the legacy dump command.
+     */
+    private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+        @Override
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, new String[] {"activities"});
+        }
+
+        @Override
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, new String[] {"settings"});
+            doDump(fd, pw, new String[] {"intents"});
+            doDump(fd, pw, new String[] {"broadcasts"});
+            doDump(fd, pw, new String[] {"providers"});
+            doDump(fd, pw, new String[] {"permissions"});
+            doDump(fd, pw, new String[] {"services"});
+            doDump(fd, pw, new String[] {"recents"});
+            doDump(fd, pw, new String[] {"lastanr"});
+            doDump(fd, pw, new String[] {"starter"});
+            if (mAssociations.size() > 0) {
+                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
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, args);
+        }
+    };
+
     public boolean canShowErrorDialogs() {
         return mShowDialogs && !mSleeping && !mShuttingDown
-                && !mKeyguardController.isKeyguardShowing()
+                && !mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)
                 && !(UserManager.isDeviceInDemoMode(mContext)
                         && mUserController.getCurrentUser().isDemo());
     }
@@ -1653,45 +1691,34 @@
     static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31;
     static final int DISPATCH_PROCESS_DIED_UI_MSG = 32;
     static final int REPORT_MEM_USAGE_MSG = 33;
-    static final int REPORT_USER_SWITCH_MSG = 34;
-    static final int CONTINUE_USER_SWITCH_MSG = 35;
-    static final int USER_SWITCH_TIMEOUT_MSG = 36;
     static final int IMMERSIVE_MODE_LOCK_MSG = 37;
     static final int PERSIST_URI_GRANTS_MSG = 38;
     static final int REQUEST_ALL_PSS_MSG = 39;
-    static final int START_PROFILES_MSG = 40;
     static final int UPDATE_TIME_PREFERENCE_MSG = 41;
-    static final int SYSTEM_USER_START_MSG = 42;
-    static final int SYSTEM_USER_CURRENT_MSG = 43;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
     static final int FINISH_BOOTING_MSG = 45;
-    static final int START_USER_SWITCH_UI_MSG = 46;
     static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
     static final int DISMISS_DIALOG_UI_MSG = 48;
     static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 49;
     static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 50;
     static final int DELETE_DUMPHEAP_MSG = 51;
-    static final int FOREGROUND_PROFILE_CHANGED_MSG = 52;
     static final int DISPATCH_UIDS_CHANGED_UI_MSG = 53;
     static final int REPORT_TIME_TRACKER_MSG = 54;
-    static final int REPORT_USER_SWITCH_COMPLETE_MSG = 55;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 56;
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;
     static final int IDLE_UIDS_MSG = 58;
-    static final int SYSTEM_USER_UNLOCK_MSG = 59;
     static final int LOG_STACK_STATE = 60;
     static final int VR_MODE_CHANGE_MSG = 61;
     static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62;
     static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
-    static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 64;
     static final int NOTIFY_VR_SLEEPING_MSG = 65;
     static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
     static final int DISPATCH_PENDING_INTENT_CANCEL_MSG = 67;
     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 USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 71;
-    static final int START_USER_SWITCH_FG_MSG = 712;
+    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;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1904,10 +1931,6 @@
                 }
                 break;
             }
-            case START_USER_SWITCH_UI_MSG: {
-                mUserController.showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
-                break;
-            }
             case DISMISS_DIALOG_UI_MSG: {
                 final Dialog d = (Dialog) msg.obj;
                 d.dismiss();
@@ -1923,6 +1946,17 @@
                 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;
@@ -2136,26 +2170,6 @@
                 thread.start();
                 break;
             }
-            case START_USER_SWITCH_FG_MSG: {
-                mUserController.startUserInForeground(msg.arg1);
-                break;
-            }
-            case REPORT_USER_SWITCH_MSG: {
-                mUserController.dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
-                break;
-            }
-            case CONTINUE_USER_SWITCH_MSG: {
-                mUserController.continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
-                break;
-            }
-            case USER_SWITCH_TIMEOUT_MSG: {
-                mUserController.timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
-                break;
-            }
-            case USER_SWITCH_CALLBACKS_TIMEOUT_MSG: {
-                mUserController.timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
-                break;
-            }
             case IMMERSIVE_MODE_LOCK_MSG: {
                 final boolean nextState = (msg.arg1 != 0);
                 if (mUpdateLock.isHeld() != nextState) {
@@ -2180,12 +2194,6 @@
                 }
                 break;
             }
-            case START_PROFILES_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    mUserController.startProfilesLocked();
-                }
-                break;
-            }
             case UPDATE_TIME_PREFERENCE_MSG: {
                 // The user's time format preference might have changed.
                 // For convenience we re-use the Intent extra values.
@@ -2204,35 +2212,6 @@
                 }
                 break;
             }
-            case SYSTEM_USER_START_MSG: {
-                mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
-                        Integer.toString(msg.arg1), msg.arg1);
-                mSystemServiceManager.startUser(msg.arg1);
-                break;
-            }
-            case SYSTEM_USER_UNLOCK_MSG: {
-                final int userId = msg.arg1;
-                mSystemServiceManager.unlockUser(userId);
-                synchronized (ActivityManagerService.this) {
-                    mRecentTasks.loadUserRecentsLocked(userId);
-                }
-                if (userId == UserHandle.USER_SYSTEM) {
-                    startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-                }
-                installEncryptionUnawareProviders(userId);
-                mUserController.finishUserUnlocked((UserState) msg.obj);
-                break;
-            }
-            case SYSTEM_USER_CURRENT_MSG: {
-                mBatteryStatsService.noteEvent(
-                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
-                        Integer.toString(msg.arg2), msg.arg2);
-                mBatteryStatsService.noteEvent(
-                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
-                        Integer.toString(msg.arg1), msg.arg1);
-                mSystemServiceManager.switchUser(msg.arg1);
-                break;
-            }
             case ENTER_ANIMATION_COMPLETE_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
@@ -2372,19 +2351,10 @@
                     mMemWatchDumpUid = -1;
                 }
             } break;
-            case FOREGROUND_PROFILE_CHANGED_MSG: {
-                mUserController.dispatchForegroundProfileChanged(msg.arg1);
-            } break;
             case REPORT_TIME_TRACKER_MSG: {
                 AppTimeTracker tracker = (AppTimeTracker)msg.obj;
                 tracker.deliverResult(mContext);
             } break;
-            case REPORT_USER_SWITCH_COMPLETE_MSG: {
-                mUserController.dispatchUserSwitchComplete(msg.arg1);
-            } break;
-            case REPORT_LOCKED_BOOT_COMPLETE_MSG: {
-                mUserController.dispatchLockedBootComplete(msg.arg1);
-            } break;
             case SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG: {
                 IUiAutomationConnection connection = (IUiAutomationConnection) msg.obj;
                 try {
@@ -2409,17 +2379,16 @@
                     if (disableNonVrUi) {
                         // If we are in a VR mode where Picture-in-Picture mode is unsupported,
                         // then remove the pinned stack.
-                        final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
-                                PINNED_STACK_ID);
-                        if (pinnedStack != null) {
-                            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
-                        }
+                        mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
                     }
                 }
             } break;
             case NOTIFY_VR_SLEEPING_MSG: {
                 notifyVrManagerOfSleepState(msg.arg1 != 0);
             } break;
+            case NOTIFY_VR_KEYGUARD_MSG: {
+                notifyVrManagerOfKeyguardState(msg.arg1 != 0);
+            } break;
             case HANDLE_TRUST_STORAGE_UPDATE_MSG: {
                 synchronized (ActivityManagerService.this) {
                     for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
@@ -2568,10 +2537,12 @@
     }
 
     public void setWindowManager(WindowManagerService wm) {
-        mWindowManager = wm;
-        mStackSupervisor.setWindowManager(wm);
-        mActivityStarter.setWindowManager(wm);
-        mLockTaskController.setWindowManager(wm);
+        synchronized (this) {
+            mWindowManager = wm;
+            mStackSupervisor.setWindowManager(wm);
+            mActivityStarter.setWindowManager(wm);
+            mLockTaskController.setWindowManager(wm);
+        }
     }
 
     public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
@@ -2589,6 +2560,14 @@
 
     static class MemBinder extends Binder {
         ActivityManagerService mActivityManagerService;
+        private final PriorityDump.PriorityDumper mPriorityDumper =
+                new PriorityDump.PriorityDumper() {
+            @Override
+            public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+                mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
+            }
+        };
+
         MemBinder(ActivityManagerService activityManagerService) {
             mActivityManagerService = activityManagerService;
         }
@@ -2597,7 +2576,7 @@
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                     "meminfo", pw)) return;
-            mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
+            PriorityDump.dump(mPriorityDumper, fd, pw, args);
         }
     }
 
@@ -2631,19 +2610,27 @@
 
     static class CpuBinder extends Binder {
         ActivityManagerService mActivityManagerService;
+        private final PriorityDump.PriorityDumper mPriorityDumper =
+                new PriorityDump.PriorityDumper() {
+            @Override
+            public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+                if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+                        "cpuinfo", pw)) return;
+                synchronized (mActivityManagerService.mProcessCpuTracker) {
+                    pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
+                    pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
+                            SystemClock.uptimeMillis()));
+                }
+            }
+        };
+
         CpuBinder(ActivityManagerService activityManagerService) {
             mActivityManagerService = activityManagerService;
         }
 
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
-                    "cpuinfo", pw)) return;
-            synchronized (mActivityManagerService.mProcessCpuTracker) {
-                pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
-                pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
-                        SystemClock.uptimeMillis()));
-            }
+            PriorityDump.dump(mPriorityDumper, fd, pw, args);
         }
     }
 
@@ -3152,9 +3139,7 @@
         }
 
         if (mLastResumedActivity != null && r.userId != mLastResumedActivity.userId) {
-            mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
-            mHandler.obtainMessage(
-                    FOREGROUND_PROFILE_CHANGED_MSG, r.userId, 0).sendToTarget();
+            mUserController.sendForegroundProfileChanged(r.userId);
         }
         mLastResumedActivity = r;
 
@@ -3259,7 +3244,8 @@
         if (r.requestedVrComponent != null && r.getStackId() >= FIRST_DYNAMIC_STACK_ID) {
             Slog.i(TAG, "Moving " + r.shortComponentName + " from stack " + r.getStackId()
                     + " to main stack for VR");
-            moveTaskToStack(r.getTask().taskId, FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */);
+            setTaskWindowingMode(r.getTask().taskId,
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
         }
         mHandler.sendMessage(
                 mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r));
@@ -3278,6 +3264,19 @@
         vrService.onSleepStateChanged(isSleeping);
     }
 
+    private void sendNotifyVrManagerOfKeyguardState(boolean isShowing) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(NOTIFY_VR_KEYGUARD_MSG, isShowing ? 1 : 0, 0));
+    }
+
+    private void notifyVrManagerOfKeyguardState(boolean isShowing) {
+        final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
+        if (vrService == null) {
+            return;
+        }
+        vrService.onKeyguardStateChanged(isShowing);
+    }
+
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
         msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
@@ -3874,6 +3873,12 @@
                 mNativeDebuggingApp = null;
             }
 
+            if (app.info.isPrivilegedApp() &&
+                    !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+                runtimeFlags |= Zygote.DISABLE_VERIFIER;
+                runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
+            }
+
             String invokeWith = null;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                 // Debuggable apps may include a wrapper script with their library directory.
@@ -4156,15 +4161,6 @@
         }
     }
 
-    void enforceShellRestriction(String restriction, int userHandle) {
-        if (Binder.getCallingUid() == SHELL_UID) {
-            if (userHandle < 0 || mUserController.hasUserRestriction(restriction, userHandle)) {
-                throw new SecurityException("Shell does not have permission to access user "
-                        + userHandle);
-            }
-        }
-    }
-
     @Override
     public int getFrontActivityScreenCompatMode() {
         enforceNotIsolatedCaller("getFrontActivityScreenCompatMode");
@@ -5432,6 +5428,7 @@
             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));
@@ -5465,6 +5462,23 @@
         }
     }
 
+    /** 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
@@ -6188,7 +6202,7 @@
                         Slog.w(TAG, "Failed trying to unstop package "
                                 + packageName + ": " + e);
                     }
-                    if (mUserController.isUserRunningLocked(user, 0)) {
+                    if (mUserController.isUserRunning(user, 0)) {
                         forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                         finishForceStopPackageLocked(packageName, pkgUid);
                     }
@@ -7346,33 +7360,32 @@
                     startProcessLocked(procs.get(ip), "on-hold", null);
                 }
             }
-
-            if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-                // Start looking for apps that are abusing wake locks.
-                Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
-                mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
-                // Tell anyone interested that we are done booting!
-                SystemProperties.set("sys.boot_completed", "1");
-
-                // And trigger dev.bootcomplete if we are not showing encryption progress
-                if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
-                    || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
-                    SystemProperties.set("dev.bootcomplete", "1");
-                }
-                mUserController.sendBootCompletedLocked(
-                        new IIntentReceiver.Stub() {
-                            @Override
-                            public void performReceive(Intent intent, int resultCode,
-                                    String data, Bundle extras, boolean ordered,
-                                    boolean sticky, int sendingUser) {
-                                synchronized (ActivityManagerService.this) {
-                                    requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-                                            true, false);
-                                }
-                            }
-                        });
-                scheduleStartProfilesLocked();
+            if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+                return;
             }
+            // Start looking for apps that are abusing wake locks.
+            Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
+            mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+            // Tell anyone interested that we are done booting!
+            SystemProperties.set("sys.boot_completed", "1");
+
+            // And trigger dev.bootcomplete if we are not showing encryption progress
+            if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
+                    || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
+                SystemProperties.set("dev.bootcomplete", "1");
+            }
+            mUserController.sendBootCompleted(
+                    new IIntentReceiver.Stub() {
+                        @Override
+                        public void performReceive(Intent intent, int resultCode,
+                                String data, Bundle extras, boolean ordered,
+                                boolean sticky, int sendingUser) {
+                            synchronized (ActivityManagerService.this) {
+                                requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+                            }
+                        }
+                    });
+            mUserController.scheduleStartProfiles();
         }
     }
 
@@ -8112,7 +8125,7 @@
                     final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
                             true /* moveHomeStackToFront */, "enterPictureInPictureMode");
-                    final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
+                    final PinnedActivityStack stack = r.getStack();
                     stack.setPictureInPictureAspectRatio(aspectRatio);
                     stack.setPictureInPictureActions(actions);
 
@@ -8227,11 +8240,6 @@
                     + ": Current activity does not support picture-in-picture.");
         }
 
-        if (!StackId.isAllowedToEnterPictureInPicture(r.getStack().getStackId())) {
-            throw new IllegalStateException(caller
-                    + ": Activities on the home, assistant, or recents stack not supported");
-        }
-
         if (params.hasSetAspectRatio()
                 && !mWindowManager.isValidPictureInPictureAspectRatio(r.getStack().mDisplayId,
                         params.getAspectRatio())) {
@@ -9869,8 +9877,9 @@
         if (tr.mBounds != null) {
             rti.bounds = new Rect(tr.mBounds);
         }
-        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreen();
+        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
         rti.resizeMode = tr.mResizeMode;
+        rti.configuration.setTo(tr.getConfiguration());
 
         ActivityRecord base = null;
         ActivityRecord top = null;
@@ -10048,7 +10057,7 @@
             enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
                     "getTaskDescription()");
             final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
             if (tr != null) {
                 return tr.lastTaskDescription;
             }
@@ -10161,7 +10170,7 @@
     public void setTaskResizeable(int taskId, int resizeableMode) {
         synchronized (this) {
             final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
-                    taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                    taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
             if (task == null) {
                 Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");
                 return;
@@ -10188,21 +10197,23 @@
                 // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
                 //   that task to freeform
                 // - otherwise the task is not moved
-                int stackId = task.getStackId();
+                ActivityStack stack = task.getStack();
                 if (!task.getWindowConfiguration().canResizeTask()) {
                     throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
                 }
-                if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
-                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-                } else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
-                    stackId = FREEFORM_WORKSPACE_STACK_ID;
+                if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                    stack = stack.getDisplay().getOrCreateStack(
+                            WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+                } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                    stack = stack.getDisplay().getOrCreateStack(
+                            WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP);
                 }
 
                 // Reparent the task to the right stack if necessary
                 boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
-                if (stackId != task.getStackId()) {
+                if (stack != task.getStack()) {
                     // Defer resume until the task is resized below
-                    task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+                    task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
                             DEFER_RESUME, "resizeTask");
                     preserveWindow = false;
                 }
@@ -10224,7 +10235,7 @@
         try {
             synchronized (this) {
                 final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
                 if (task == null) {
                     Slog.w(TAG, "getTaskBounds: taskId=" + taskId + " not found");
                     return rect;
@@ -10256,7 +10267,7 @@
         try {
             synchronized (this) {
                 final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "cancelTaskWindowTransition: taskId=" + taskId + " not found");
                     return;
@@ -10275,7 +10286,7 @@
         try {
             synchronized (this) {
                 final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found");
                     return;
@@ -10295,7 +10306,7 @@
             final TaskRecord task;
             synchronized (this) {
                 task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
                 if (task == null) {
                     Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
                     return null;
@@ -10375,13 +10386,14 @@
     @Override
     public void removeStack(int stackId) {
         enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException("Removing home or recents stack is not allowed.");
-        }
-
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
+                if (stack != null && !stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException(
+                            "Removing non-standard stack is not allowed.");
+                }
                 mStackSupervisor.removeStackLocked(stackId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10389,6 +10401,36 @@
         }
     }
 
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    @Override
+    public void removeStacksInWindowingModes(int[] windowingModes) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksInWindowingModes()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mStackSupervisor.removeStacksInWindowingModes(windowingModes);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void removeStacksWithActivityTypes(int[] activityTypes) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksWithActivityTypes()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mStackSupervisor.removeStacksWithActivityTypes(activityTypes);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
     @Override
     public void moveStackToDisplay(int stackId, int displayId) {
         enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, "moveStackToDisplay()");
@@ -10532,13 +10574,15 @@
     public int createStackOnDisplay(int displayId) throws RemoteException {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "createStackOnDisplay()");
         synchronized (this) {
-            final int stackId = mStackSupervisor.getNextStackId();
-            final ActivityStack stack =
-                    mStackSupervisor.createStackOnDisplay(stackId, displayId, true /*onTop*/);
-            if (stack == null) {
+            final ActivityDisplay display =
+                    mStackSupervisor.getActivityDisplayOrCreateLocked(displayId);
+            if (display == null) {
                 return INVALID_STACK_ID;
             }
-            return stack.mStackId;
+            // TODO(multi-display): Have the caller pass in the windowing mode and activity type.
+            final ActivityStack stack = display.createStack(
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/);
+            return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
         }
     }
 
@@ -10570,8 +10614,12 @@
                             "exitFreeformMode: You can only go fullscreen from freeform.");
                 }
 
+                final ActivityStack fullscreenStack = stack.getDisplay().getOrCreateStack(
+                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
-                r.getTask().reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+                // TODO: Should just change windowing mode vs. re-parenting...
+                r.getTask().reparent(fullscreenStack, ON_TOP,
                         REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "exitFreeformMode");
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10580,12 +10628,44 @@
     }
 
     @Override
+    public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                if (task == null) {
+                    Slog.w(TAG, "setTaskWindowingMode: No task for id=" + taskId);
+                    return;
+                }
+
+                if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
+                        + " to windowingMode=" + windowingMode + " toTop=" + toTop);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                    mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
+                            null /* initialBounds */);
+                }
+
+                if (!task.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move task "
+                            + taskId + " to non-standard windowin mode=" + windowingMode);
+                }
+                final ActivityDisplay display = task.getStack().getDisplay();
+                final ActivityStack stack = display.getOrCreateStack(windowingMode,
+                        task.getStack().getActivityType(), toTop);
+                // TODO: We should just change the windowing mode for the task vs. creating and
+                // moving it to a stack.
+                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+                        "moveTaskToStack");
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException(
-                    "moveTaskToStack: Attempt to move task " + taskId + " to stack " + stackId);
-        }
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10601,58 +10681,25 @@
                     mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
                             null /* initialBounds */);
                 }
-                task.reparent(stackId, toTop,
-                        REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack");
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
 
-    @Override
-    public void swapDockedAndFullscreenStack() throws RemoteException {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "swapDockedAndFullscreenStack()");
-        synchronized (this) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                final ActivityStack fullscreenStack = mStackSupervisor.getStack(
-                        FULLSCREEN_WORKSPACE_STACK_ID);
-                final TaskRecord topTask = fullscreenStack != null ? fullscreenStack.topTask()
-                        : null;
-                final ActivityStack dockedStack = mStackSupervisor.getStack(DOCKED_STACK_ID);
-                final ArrayList<TaskRecord> tasks = dockedStack != null ? dockedStack.getAllTasks()
-                        : null;
-                if (topTask == null || tasks == null || tasks.size() == 0) {
-                    Slog.w(TAG,
-                            "Unable to swap tasks, either docked or fullscreen stack is empty.");
-                    return;
-                }
-
-                // TODO: App transition
-                mWindowManager.prepareAppTransition(TRANSIT_ACTIVITY_RELAUNCH, false);
-
-                // Defer the resume until we move all the docked tasks to the fullscreen stack below
-                topTask.reparent(DOCKED_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
-                        DEFER_RESUME, "swapDockedAndFullscreenStack - DOCKED_STACK");
-                final int size = tasks.size();
-                for (int i = 0; i < size; i++) {
-                    final int id = tasks.get(i).taskId;
-                    if (id == topTask.taskId) {
-                        continue;
+                ActivityStack stack = mStackSupervisor.getStack(stackId);
+                if (stack == null) {
+                    if (!isStaticStack(stackId)) {
+                        throw new IllegalStateException(
+                                "moveTaskToStack: No stack for stackId=" + stackId);
                     }
-
-                    // Defer the resume until after all the tasks have been moved
-                    tasks.get(i).reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
-                            REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, DEFER_RESUME,
-                            "swapDockedAndFullscreenStack - FULLSCREEN_STACK");
+                    final ActivityDisplay display = task.getStack().getDisplay();
+                    final int windowingMode =
+                            getWindowingModeForStackId(stackId, display.hasSplitScreenStack());
+                    stack = display.getOrCreateStack(windowingMode,
+                            task.getStack().getActivityType(), toTop);
                 }
-
-                // Because we deferred the resume to avoid conflicts with stack switches while
-                // resuming, we need to do it after all the tasks are moved.
-                mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-                mStackSupervisor.resumeFocusedStackTopActivityLocked();
-
-                mWindowManager.executeAppTransition();
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("moveTaskToStack: Attempt to move task "
+                            + taskId + " to stack " + stackId);
+                }
+                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+                        "moveTaskToStack");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10685,13 +10732,18 @@
                     Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
                     return false;
                 }
-
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
                         + " to createMode=" + createMode + " toTop=" + toTop);
                 mWindowManager.setDockedStackCreateState(createMode, initialBounds);
 
+                final ActivityDisplay display = task.getStack().getDisplay();
+                final ActivityStack stack = display.getOrCreateStack(
+                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, task.getStack().getActivityType(),
+                        toTop);
+
                 // Defer resuming until we move the home stack to the front below
-                final boolean moved = task.reparent(DOCKED_STACK_ID, toTop,
+                // TODO: Should just change windowing mode vs. re-parenting...
+                final boolean moved = task.reparent(stack, toTop,
                         REPARENT_KEEP_STACK_AT_FRONT, animate, !DEFER_RESUME,
                         "moveTaskToDockedStack");
                 if (moved) {
@@ -10705,6 +10757,66 @@
     }
 
     /**
+     * Dismisses split-screen multi-window mode.
+     * @param toTop If true the current primary split-screen stack will be placed or left on top.
+     */
+    @Override
+    public void dismissSplitScreenMode(boolean toTop) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final ActivityStack stack =
+                        mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+                if (toTop) {
+                    mStackSupervisor.resizeStackLocked(stack.mStackId, null /* destBounds */,
+                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                            true /* preserveWindows */, true /* allowResizeInDockedMode */,
+                            !DEFER_RESUME);
+                } else {
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, false /* onTop */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Dismisses Pip
+     * @param animate True if the dismissal should be animated.
+     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
+     *                          default animation duration should be used.
+     */
+    @Override
+    public void dismissPip(boolean animate, int animationDuration) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final PinnedActivityStack stack =
+                        mStackSupervisor.getDefaultDisplay().getPinnedStack();
+
+                if (stack == null) {
+                    return;
+                }
+                if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
+                    throw new IllegalArgumentException("Stack: " + stack
+                            + " doesn't support animated resize.");
+                }
+                if (animate) {
+                    stack.animateResizePinnedStack(null /* sourceHintBounds */,
+                            null /* destBounds */, animationDuration, false /* fromFullscreen */);
+                } else {
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, true /* onTop */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
      * Moves the top activity in the input stackId to the pinned stack.
      *
      * @param stackId Id of stack to move the top activity to pinned stack.
@@ -10739,17 +10851,16 @@
         try {
             synchronized (this) {
                 if (animate) {
-                    if (stackId == PINNED_STACK_ID) {
-                        final PinnedActivityStack pinnedStack =
-                                mStackSupervisor.getStack(PINNED_STACK_ID);
-                        if (pinnedStack != null) {
-                            pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */,
-                                    destBounds, animationDuration, false /* fromFullscreen */);
-                        }
-                    } else {
+                    final PinnedActivityStack stack = mStackSupervisor.getStack(stackId);
+                    if (stack == null) {
+                        return;
+                    }
+                    if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
                         throw new IllegalArgumentException("Stack: " + stackId
                                 + " doesn't support animated resize.");
                     }
+                    stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
+                            animationDuration, false /* fromFullscreen */);
                 } else {
                     mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */,
                             null /* tempTaskInsetBounds */, preserveWindows,
@@ -10801,11 +10912,6 @@
     @Override
     public void positionTaskInStack(int taskId, int stackId, int position) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "positionTaskInStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException(
-                    "positionTaskInStack: Attempt to change the position of task "
-                    + taskId + " in/to home/recents stack");
-        }
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10817,8 +10923,16 @@
                             + taskId);
                 }
 
-                final ActivityStack stack = mStackSupervisor.getStack(stackId, CREATE_IF_NEEDED,
-                        !ON_TOP);
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
+
+                if (stack == null) {
+                    throw new IllegalArgumentException("positionTaskInStack: no stack for id="
+                            + stackId);
+                }
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("positionTaskInStack: Attempt to change"
+                            + " the position of task " + taskId + " in/to non-standard stack");
+                }
 
                 // TODO: Have the callers of this API call a separate reparent method if that is
                 // what they intended to do vs. having this method also do reparenting.
@@ -10827,8 +10941,8 @@
                     stack.positionChildAt(task, position);
                 } else {
                     // Reparent to new stack.
-                    task.reparent(stackId, position, REPARENT_LEAVE_STACK_IN_PLACE,
-                            !ANIMATE, !DEFER_RESUME, "positionTaskInStack");
+                    task.reparent(stack, position, REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
+                            !DEFER_RESUME, "positionTaskInStack");
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10850,12 +10964,12 @@
     }
 
     @Override
-    public StackInfo getStackInfo(int stackId) {
+    public StackInfo getStackInfo(int windowingMode, int activityType) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                return mStackSupervisor.getStackInfoLocked(stackId);
+                return mStackSupervisor.getStackInfo(windowingMode, activityType);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -10882,7 +10996,6 @@
 
     @Override
     public void updateLockTaskPackages(int userId, String[] packages) {
-        // TODO: move this into LockTaskController
         final int callingUid = Binder.getCallingUid();
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
@@ -10891,8 +11004,7 @@
         synchronized (this) {
             if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" +
                     Arrays.toString(packages));
-            mLockTaskPackages.put(userId, packages);
-            mLockTaskController.onLockTaskPackagesUpdated();
+            mLockTaskController.updateLockTaskPackages(userId, packages);
         }
     }
 
@@ -10908,11 +11020,7 @@
         }
 
         // When a task is locked, dismiss the pinned stack if it exists
-        final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
-                PINNED_STACK_ID);
-        if (pinnedStack != null) {
-            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
-        }
+        mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
 
         // isAppPinning is used to distinguish between locked and pinned mode, as pinned mode
         // is initiated by system after the pinning request was shown and locked mode is initiated
@@ -11139,7 +11247,7 @@
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
-            int tmpTargetUserId = mUserController.unsafeConvertIncomingUserLocked(userId);
+            int tmpTargetUserId = mUserController.unsafeConvertIncomingUser(userId);
             if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
                 if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
                     return null;
@@ -11527,7 +11635,7 @@
 
                 // Make sure that the user who owns this provider is running.  If not,
                 // we don't want to allow it to run.
-                if (!mUserController.isUserRunningLocked(userId, 0)) {
+                if (!mUserController.isUserRunning(userId, 0)) {
                     Slog.w(TAG, "Unable to launch app "
                             + cpi.applicationInfo.packageName + "/"
                             + cpi.applicationInfo.uid + " for provider "
@@ -12073,7 +12181,7 @@
         //mUsageStatsService.monitorPackages();
     }
 
-    private void startPersistentApps(int matchFlags) {
+    void startPersistentApps(int matchFlags) {
         if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;
 
         synchronized (this) {
@@ -12094,7 +12202,7 @@
      * When a user is unlocked, we need to install encryption-unaware providers
      * belonging to any running apps.
      */
-    private void installEncryptionUnawareProviders(int userId) {
+    void installEncryptionUnawareProviders(int userId) {
         // We're only interested in providers that are encryption unaware, and
         // we don't care about uninstalled apps, since there's no way they're
         // running at this point.
@@ -12157,9 +12265,7 @@
         int callingPid = Binder.getCallingPid();
         long ident = 0;
         boolean clearedIdentity = false;
-        synchronized (this) {
-            userId = mUserController.unsafeConvertIncomingUserLocked(userId);
-        }
+        userId = mUserController.unsafeConvertIncomingUser(userId);
         if (canClearIdentity(callingPid, callingUid, userId)) {
             clearedIdentity = true;
             ident = Binder.clearCallingIdentity();
@@ -12399,19 +12505,14 @@
 
     void onWakefulnessChanged(int wakefulness) {
         synchronized(this) {
+            boolean wasAwake = mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
+            boolean isAwake = wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
             mWakefulness = wakefulness;
 
-            // Also update state in a special way for running foreground services UI.
-            switch (mWakefulness) {
-                case PowerManagerInternal.WAKEFULNESS_ASLEEP:
-                case PowerManagerInternal.WAKEFULNESS_DREAMING:
-                case PowerManagerInternal.WAKEFULNESS_DOZING:
-                    mServices.updateScreenStateLocked(false /* screenOn */);
-                    break;
-                case PowerManagerInternal.WAKEFULNESS_AWAKE:
-                default:
-                    mServices.updateScreenStateLocked(true /* screenOn */);
-                    break;
+            if (wasAwake != isAwake) {
+                // Also update state in a special way for running foreground services UI.
+                mServices.updateScreenStateLocked(isAwake);
+                sendNotifyVrManagerOfSleepState(!isAwake);
             }
         }
     }
@@ -12447,7 +12548,6 @@
             }
             mStackSupervisor.applySleepTokensLocked(true /* applyToStacks */);
             if (wasSleeping) {
-                sendNotifyVrManagerOfSleepState(false);
                 updateOomAdjLocked();
             }
         } else if (!mSleeping && shouldSleep) {
@@ -12457,7 +12557,6 @@
             }
             mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
             mStackSupervisor.goingToSleepLocked();
-            sendNotifyVrManagerOfSleepState(true);
             updateOomAdjLocked();
         }
     }
@@ -12551,7 +12650,7 @@
     }
 
     @Override
-    public void setLockScreenShown(boolean showing) {
+    public void setLockScreenShown(boolean showing, int secondaryDisplayShowing) {
         if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires permission "
@@ -12561,11 +12660,12 @@
         synchronized(this) {
             long ident = Binder.clearCallingIdentity();
             try {
-                mKeyguardController.setKeyguardShown(showing);
+                mKeyguardController.setKeyguardShown(showing, secondaryDisplayShowing);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
+        sendNotifyVrManagerOfKeyguardState(showing);
     }
 
     @Override
@@ -12584,7 +12684,7 @@
                 if (mUserController.shouldConfirmCredentials(userId)) {
                     if (mKeyguardController.isKeyguardLocked()) {
                         // Showing launcher to avoid user entering credential twice.
-                        final int currentUserId = mUserController.getCurrentUserIdLocked();
+                        final int currentUserId = mUserController.getCurrentUserId();
                         startHomeActivityLocked(currentUserId, "notifyLockedProfile");
                     }
                     mStackSupervisor.lockAllProfileTasks(userId);
@@ -13982,10 +14082,10 @@
                 mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                         || Settings.Global.getInt(
                                 resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
-        final boolean supportsPictureInPicture =
-                mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
 
         final boolean supportsMultiWindow = ActivityManager.supportsMultiWindow(mContext);
+        final boolean supportsPictureInPicture = supportsMultiWindow &&
+                mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
         final boolean supportsSplitScreenMultiWindow =
                 ActivityManager.supportsSplitScreenMultiWindow(mContext);
         final boolean supportsMultiDisplay = mContext.getPackageManager()
@@ -14164,9 +14264,8 @@
         }
 
         retrieveSettings();
-        final int currentUserId;
+        final int currentUserId = mUserController.getCurrentUserId();
         synchronized (this) {
-            currentUserId = mUserController.getCurrentUserIdLocked();
             readGrantedUriPermissionsLocked();
         }
 
@@ -14245,7 +14344,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
             mStackSupervisor.resumeFocusedStackTopActivityLocked();
-            mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
+            mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
             traceLog.traceEnd(); // ActivityManagerStartApps
             traceLog.traceEnd(); // PhaseActivityManagerReady
         }
@@ -14597,6 +14696,9 @@
                 }
                 sb.append("\n");
             }
+            if (process.info.isInstantApp()) {
+                sb.append("Instant-App: true\n");
+            }
         }
     }
 
@@ -14943,6 +15045,13 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        PriorityDump.dump(mPriorityDumper, fd, pw, args);
+    }
+
+    /**
+     * Wrapper function to print out debug data filtered by specified arguments.
+    */
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
         boolean dumpAll = false;
@@ -14951,6 +15060,7 @@
         boolean dumpCheckinFormat = false;
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
+        boolean useProto = false;
         String dumpPackage = null;
 
         int opti = 0;
@@ -14984,12 +15094,26 @@
             } else if ("-h".equals(opt)) {
                 ActivityManagerShellCommand.dumpHelp(pw, true);
                 return;
+            } else if ("--proto".equals(opt)) {
+                useProto = true;
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
             }
         }
 
         long origId = Binder.clearCallingIdentity();
+
+        if (useProto) {
+            //TODO: Options when dumping proto
+            final ProtoOutputStream proto = new ProtoOutputStream(fd);
+            synchronized (this) {
+                writeActivitiesToProtoLocked(proto);
+            }
+            proto.flush();
+            Binder.restoreCallingIdentity(origId);
+            return;
+        }
+
         boolean more = false;
         // Is the caller requesting to dump a particular piece of data?
         if (opti < args.length) {
@@ -15333,6 +15457,10 @@
         Binder.restoreCallingIdentity(origId);
     }
 
+    private void writeActivitiesToProtoLocked(ProtoOutputStream proto) {
+        mStackSupervisor.writeToProto(proto, ACTIVITIES);
+    }
+
     private void dumpLastANRLocked(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
         if (mLastANRState == null) {
@@ -19047,7 +19175,7 @@
         // If not, we will just skip it. Make an exception for shutdown broadcasts
         // and upgrade steps.
 
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, 0)) {
+        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) {
             if ((callingUid != SYSTEM_UID
                     || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                     && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
@@ -19466,7 +19594,7 @@
         int[] users;
         if (userId == UserHandle.USER_ALL) {
             // Caller wants broadcast to go to all started users.
-            users = mUserController.getStartedUserArrayLocked();
+            users = mUserController.getStartedUserArray();
         } else {
             // Caller wants broadcast to go to one specific user.
             users = new int[] {userId};
@@ -20092,12 +20220,20 @@
     }
 
     @Override
-    public int getFocusedStackId() throws RemoteException {
-        ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack != null) {
-            return focusedStack.getStackId();
+    public StackInfo getFocusedStackInfo() throws RemoteException {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                ActivityStack focusedStack = getFocusedStack();
+                if (focusedStack != null) {
+                    return mStackSupervisor.getStackInfo(focusedStack.mStackId);
+                }
+                return null;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
-        return -1;
     }
 
     public Configuration getConfiguration() {
@@ -20123,15 +20259,20 @@
      *       activity and clearing the task at the same time.
      */
     @Override
+    // TODO: API should just be about changing windowing modes...
     public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
-        if (StackId.isHomeOrRecentsStack(fromStackId)) {
-            throw new IllegalArgumentException("You can't move tasks from the home/recents stack.");
-        }
         synchronized (this) {
             final long origId = Binder.clearCallingIdentity();
             try {
-                mStackSupervisor.moveTasksToFullscreenStackLocked(fromStackId, onTop);
+                final ActivityStack stack = mStackSupervisor.getStack(fromStackId);
+                if (stack != null){
+                    if (!stack.isActivityTypeStandardOrUndefined()) {
+                        throw new IllegalArgumentException(
+                                "You can't move tasks from non-standard stacks.");
+                    }
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, onTop);
+                }
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -20229,7 +20370,7 @@
 
     void updateUserConfigurationLocked() {
         final Configuration configuration = new Configuration(getGlobalConfiguration());
-        final int currentUserId = mUserController.getCurrentUserIdLocked();
+        final int currentUserId = mUserController.getCurrentUserId();
         Settings.System.adjustConfigurationForUser(mContext.getContentResolver(), configuration,
                 currentUserId, Settings.System.canWrite(mContext));
         updateConfigurationLocked(configuration, null /* starting */, false /* initLocale */,
@@ -20340,7 +20481,7 @@
         Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
         // TODO(multi-display): Update UsageEvents#Event to include displayId.
         mUsageStatsService.reportConfigurationChange(mTempConfig,
-                mUserController.getCurrentUserIdLocked());
+                mUserController.getCurrentUserId());
 
         // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
         mShowDialogs = shouldShowDialogs(mTempConfig);
@@ -22271,7 +22412,7 @@
             String authority) {
         if (app == null) return;
         if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-            UserState userState = mUserController.getStartedUserStateLocked(app.userId);
+            UserState userState = mUserController.getStartedUserState(app.userId);
             if (userState == null) return;
             final long now = SystemClock.elapsedRealtime();
             Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -23566,54 +23707,7 @@
 
     @Override
     public boolean switchUser(final int targetUserId) {
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
-        int currentUserId;
-        UserInfo targetUserInfo;
-        synchronized (this) {
-            currentUserId = mUserController.getCurrentUserIdLocked();
-            targetUserInfo = mUserController.getUserInfo(targetUserId);
-            if (targetUserId == currentUserId) {
-                Slog.i(TAG, "user #" + targetUserId + " is already the current user");
-                return true;
-            }
-            if (targetUserInfo == null) {
-                Slog.w(TAG, "No user info for user #" + targetUserId);
-                return false;
-            }
-            if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mContext)) {
-                Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
-                        + " when device is in demo mode");
-                return false;
-            }
-            if (!targetUserInfo.supportsSwitchTo()) {
-                Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
-                return false;
-            }
-            if (targetUserInfo.isManagedProfile()) {
-                Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
-                return false;
-            }
-            mUserController.setTargetUserIdLocked(targetUserId);
-        }
-        if (mUserController.mUserSwitchUiEnabled) {
-            UserInfo currentUserInfo = mUserController.getUserInfo(currentUserId);
-            Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
-            mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
-            mUiHandler.sendMessage(mHandler.obtainMessage(
-                    START_USER_SWITCH_UI_MSG, userNames));
-        } else {
-            mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
-            mHandler.sendMessage(mHandler.obtainMessage(
-                    START_USER_SWITCH_FG_MSG, targetUserId, 0));
-        }
-        return true;
-    }
-
-    void scheduleStartProfilesLocked() {
-        if (!mHandler.hasMessages(START_PROFILES_MSG)) {
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
-                    DateUtils.SECOND_IN_MILLIS);
-        }
+        return mUserController.switchUser(targetUserId);
     }
 
     @Override
@@ -23627,10 +23721,8 @@
     }
 
     String getStartedUserState(int userId) {
-        synchronized (this) {
-            final UserState userState = mUserController.getStartedUserStateLocked(userId);
-            return UserState.stateToString(userState.state);
-        }
+        final UserState userState = mUserController.getStartedUserState(userId);
+        return UserState.stateToString(userState.state);
     }
 
     @Override
@@ -23645,9 +23737,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        synchronized (this) {
-            return mUserController.isUserRunningLocked(userId, flags);
-        }
+        return mUserController.isUserRunning(userId, flags);
     }
 
     @Override
@@ -23661,9 +23751,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        synchronized (this) {
-            return mUserController.getStartedUserArrayLocked();
-        }
+        return mUserController.getStartedUserArray();
     }
 
     @Override
@@ -23684,9 +23772,7 @@
     }
 
     public boolean isUserStopped(int userId) {
-        synchronized (this) {
-            return mUserController.getStartedUserStateLocked(userId) == null;
-        }
+        return mUserController.getStartedUserState(userId) == null;
     }
 
     ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
@@ -24025,7 +24111,7 @@
         @Override
         public void notifyKeyguardTrustedChanged() {
             synchronized (ActivityManagerService.this) {
-                if (mKeyguardController.isKeyguardShowing()) {
+                if (mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
                     mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 }
             }
@@ -24274,7 +24360,7 @@
                 permission.INTERACT_ACROSS_USERS_FULL, "getLastResumedActivityUserId()");
         synchronized (this) {
             if (mLastResumedActivity == null) {
-                return mUserController.getCurrentUserIdLocked();
+                return mUserController.getCurrentUserId();
             }
             return mLastResumedActivity.userId;
         }
@@ -24481,7 +24567,7 @@
                 if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
                     try {
                         final ApplicationInfo ai = AppGlobals.getPackageManager()
-                                .getApplicationInfo(packageName, 0 /*flags*/, app.userId);
+                                .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
                         if (ai != null) {
                             app.thread.scheduleApplicationInfoChanged(ai);
                         }
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index 6901d2d..4c93423 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -75,6 +75,9 @@
 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;
 
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
@@ -115,7 +118,8 @@
     private boolean mStreaming;   // Streaming the profiling output to a file.
     private String mAgent;  // Agent to attach on startup.
     private int mDisplayId;
-    private int mStackId;
+    private int mWindowingMode;
+    private int mActivityType;
     private int mTaskId;
     private boolean mIsTaskOverlay;
 
@@ -271,7 +275,8 @@
         mStreaming = false;
         mUserId = defUser;
         mDisplayId = INVALID_DISPLAY;
-        mStackId = INVALID_STACK_ID;
+        mWindowingMode = WINDOWING_MODE_UNDEFINED;
+        mActivityType = ACTIVITY_TYPE_UNDEFINED;
         mTaskId = INVALID_TASK_ID;
         mIsTaskOverlay = false;
 
@@ -308,8 +313,10 @@
                     mReceiverPermission = getNextArgRequired();
                 } else if (opt.equals("--display")) {
                     mDisplayId = Integer.parseInt(getNextArgRequired());
-                } else if (opt.equals("--stack")) {
-                    mStackId = Integer.parseInt(getNextArgRequired());
+                } else if (opt.equals("--windowingMode")) {
+                    mWindowingMode = Integer.parseInt(getNextArgRequired());
+                } else if (opt.equals("--activityType")) {
+                    mActivityType = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("--task")) {
                     mTaskId = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("--task-overlay")) {
@@ -396,9 +403,17 @@
                 options = ActivityOptions.makeBasic();
                 options.setLaunchDisplayId(mDisplayId);
             }
-            if (mStackId != INVALID_STACK_ID) {
-                options = ActivityOptions.makeBasic();
-                options.setLaunchStackId(mStackId);
+            if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
+                if (options == null) {
+                    options = ActivityOptions.makeBasic();
+                }
+                options.setLaunchWindowingMode(mWindowingMode);
+            }
+            if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
+                if (options == null) {
+                    options = ActivityOptions.makeBasic();
+                }
+                options.setLaunchActivityType(mActivityType);
             }
             if (mTaskId != INVALID_TASK_ID) {
                 options = ActivityOptions.makeBasic();
@@ -2099,9 +2114,9 @@
     }
 
     int runStackInfo(PrintWriter pw) throws RemoteException {
-        String stackIdStr = getNextArgRequired();
-        int stackId = Integer.parseInt(stackIdStr);
-        ActivityManager.StackInfo info = mInterface.getStackInfo(stackId);
+        int windowingMode = Integer.parseInt(getNextArgRequired());
+        int activityType = Integer.parseInt(getNextArgRequired());
+        ActivityManager.StackInfo info = mInterface.getStackInfo(windowingMode, activityType);
         pw.println(info);
         return 0;
     }
@@ -2135,7 +2150,8 @@
         final String delayStr = getNextArg();
         final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
 
-        ActivityManager.StackInfo info = mInterface.getStackInfo(DOCKED_STACK_ID);
+        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;
@@ -2238,10 +2254,6 @@
             return runTaskResizeable(pw);
         } else if (op.equals("resize")) {
             return runTaskResize(pw);
-        } else if (op.equals("drag-task-test")) {
-            return runTaskDragTaskTest(pw);
-        } else if (op.equals("size-task-test")) {
-            return runTaskSizeTaskTest(pw);
         } else if (op.equals("focus")) {
             return runTaskFocus(pw);
         } else {
@@ -2294,58 +2306,6 @@
         }
     }
 
-    int runTaskDragTaskTest(PrintWriter pw) throws RemoteException {
-        final int taskId = Integer.parseInt(getNextArgRequired());
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String delayStr = getNextArg();
-        final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-        final ActivityManager.StackInfo stackInfo;
-        Rect taskBounds;
-        stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
-        taskBounds = mInterface.getTaskBounds(taskId);
-        final Rect stackBounds = stackInfo.bounds;
-        int travelRight = stackBounds.width() - taskBounds.width();
-        int travelLeft = -travelRight;
-        int travelDown = stackBounds.height() - taskBounds.height();
-        int travelUp = -travelDown;
-        int passes = 0;
-
-        // We do 2 passes to get back to the original location of the task.
-        while (passes < 2) {
-            // Move right
-            pw.println("Moving right...");
-            pw.flush();
-            travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel right by " + travelRight);
-
-            // Move down
-            pw.println("Moving down...");
-            pw.flush();
-            travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel down by " + travelDown);
-
-            // Move left
-            pw.println("Moving left...");
-            pw.flush();
-            travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel left by " + travelLeft);
-
-            // Move up
-            pw.println("Moving up...");
-            pw.flush();
-            travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel up by " + travelUp);
-
-            taskBounds = mInterface.getTaskBounds(taskId);
-            passes++;
-        }
-        return 0;
-    }
-
     int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize,
             int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms)
             throws RemoteException {
@@ -2408,133 +2368,6 @@
         return stepSize;
     }
 
-    int runTaskSizeTaskTest(PrintWriter pw) throws RemoteException {
-        final int taskId = Integer.parseInt(getNextArgRequired());
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String delayStr = getNextArg();
-        final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-        final ActivityManager.StackInfo stackInfo;
-        final Rect initialTaskBounds;
-        stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
-        initialTaskBounds = mInterface.getTaskBounds(taskId);
-        final Rect stackBounds = stackInfo.bounds;
-        stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET);
-        final Rect currentTaskBounds = new Rect(initialTaskBounds);
-
-        // Size by top-left
-        pw.println("Growing top-left");
-        pw.flush();
-        do {
-            currentTaskBounds.top -= getStepSize(
-                    currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.left -= getStepSize(
-                    currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.top < currentTaskBounds.top
-                || stackBounds.left < currentTaskBounds.left);
-
-        // Back to original size
-        pw.println("Shrinking top-left");
-        pw.flush();
-        do {
-            currentTaskBounds.top += getStepSize(
-                    currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.left += getStepSize(
-                    currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.top > currentTaskBounds.top
-                || initialTaskBounds.left > currentTaskBounds.left);
-
-        // Size by top-right
-        pw.println("Growing top-right");
-        pw.flush();
-        do {
-            currentTaskBounds.top -= getStepSize(
-                    currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.right += getStepSize(
-                    currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.top < currentTaskBounds.top
-                || stackBounds.right > currentTaskBounds.right);
-
-        // Back to original size
-        pw.println("Shrinking top-right");
-        pw.flush();
-        do {
-            currentTaskBounds.top += getStepSize(
-                    currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
-                    stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.top > currentTaskBounds.top
-                || initialTaskBounds.right < currentTaskBounds.right);
-
-        // Size by bottom-left
-        pw.println("Growing bottom-left");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom += getStepSize(
-                    currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.left -= getStepSize(
-                    currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.bottom > currentTaskBounds.bottom
-                || stackBounds.left < currentTaskBounds.left);
-
-        // Back to original size
-        pw.println("Shrinking bottom-left");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
-                    initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.left += getStepSize(
-                    currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.bottom < currentTaskBounds.bottom
-                || initialTaskBounds.left > currentTaskBounds.left);
-
-        // Size by bottom-right
-        pw.println("Growing bottom-right");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom += getStepSize(
-                    currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.right += getStepSize(
-                    currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.bottom > currentTaskBounds.bottom
-                || stackBounds.right > currentTaskBounds.right);
-
-        // Back to original size
-        pw.println("Shrinking bottom-right");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
-                    initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
-                    stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.bottom < currentTaskBounds.bottom
-                || initialTaskBounds.right < currentTaskBounds.right);
-        return 0;
-    }
-
     int runTaskFocus(PrintWriter pw) throws RemoteException {
         final int taskId = Integer.parseInt(getNextArgRequired());
         pw.println("Setting focus to task " + taskId);
@@ -2661,6 +2494,7 @@
             pw.println("  -p: limit output to given package.");
             pw.println("  --checkin: output checkin format, resetting data.");
             pw.println("  --C: output checkin format, not resetting data.");
+            pw.println("  --proto: output dump in protocol buffer format.");
         } else {
             pw.println("Activity manager (activity) commands:");
             pw.println("  help");
@@ -2685,7 +2519,8 @@
             pw.println("      --track-allocation: enable tracking of object allocations");
             pw.println("      --user <USER_ID> | current: Specify which user to run as; if not");
             pw.println("          specified then run as the current user.");
-            pw.println("      --stack <STACK_ID>: Specify into which stack should the activity be put.");
+            pw.println("      --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.");
+            pw.println("      --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.");
             pw.println("  start-service [--user <USER_ID> | current] <INTENT>");
             pw.println("      Start a Service.  Options are:");
             pw.println("      --user <USER_ID> | current: Specify which user to run as; if not");
@@ -2864,8 +2699,8 @@
             pw.println("           Place <TASK_ID> in <STACK_ID> at <POSITION>");
             pw.println("       list");
             pw.println("           List all of the activity stacks and their sizes.");
-            pw.println("       info <STACK_ID>");
-            pw.println("           Display the information about activity stack <STACK_ID>.");
+            pw.println("       info <WINDOWING_MODE> <ACTIVITY_TYPE>");
+            pw.println("           Display the information about activity stack in <WINDOWING_MODE> and <ACTIVITY_TYPE>.");
             pw.println("       remove <STACK_ID>");
             pw.println("           Remove stack <STACK_ID>.");
             pw.println("  task [COMMAND] [...]: sub-commands for operating on activity tasks.");
@@ -2883,14 +2718,6 @@
             pw.println("           Makes sure <TASK_ID> is in a stack with the specified bounds.");
             pw.println("           Forces the task to be resizeable and creates a stack if no existing stack");
             pw.println("           has the specified bounds.");
-            pw.println("       drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
-            pw.println("           Test command for dragging/moving <TASK_ID> by");
-            pw.println("           <STEP_SIZE> increments around the screen applying the optional [DELAY_MS]");
-            pw.println("           between each step.");
-            pw.println("       size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
-            pw.println("           Test command for sizing <TASK_ID> by <STEP_SIZE>");
-            pw.println("           increments within the screen applying the optional [DELAY_MS] between");
-            pw.println("           each step.");
             pw.println("  update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
             pw.println("      Update the ApplicationInfo objects of the listed packages for <USER_ID>");
             pw.println("      without restarting any processes.");
diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java
index 0c8321d..fdcb8c6 100644
--- a/com/android/server/am/ActivityMetricsLogger.java
+++ b/com/android/server/am/ActivityMetricsLogger.java
@@ -2,12 +2,9 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 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;
@@ -32,13 +29,10 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
 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.ActivityStack.STACK_INVISIBLE;
 
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.metrics.LogMaker;
 import android.os.SystemClock;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 0ccb45f..7b0b942 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -17,8 +17,6 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-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.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
@@ -36,7 +34,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.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.content.Intent.ACTION_MAIN;
@@ -89,10 +86,8 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SAVED_STATE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SCREENSHOTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_THUMBNAILS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -107,7 +102,6 @@
 import static com.android.server.am.ActivityStack.LAUNCH_TICK;
 import static com.android.server.am.ActivityStack.LAUNCH_TICK_MSG;
 import static com.android.server.am.ActivityStack.PAUSE_TIMEOUT_MSG;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 import static com.android.server.am.ActivityStack.STOP_TIMEOUT_MSG;
 import static com.android.server.am.EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME;
 import static com.android.server.am.EventLogTags.AM_ACTIVITY_LAUNCH_TIME;
@@ -116,6 +110,16 @@
 import static com.android.server.am.TaskPersister.DEBUG;
 import static com.android.server.am.TaskPersister.IMAGE_EXTENSION;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.proto.ActivityRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityRecordProto.FRONT_OF_TASK;
+import static com.android.server.am.proto.ActivityRecordProto.IDENTIFIER;
+import static com.android.server.am.proto.ActivityRecordProto.PROC_ID;
+import static com.android.server.am.proto.ActivityRecordProto.STATE;
+import static com.android.server.am.proto.ActivityRecordProto.VISIBLE;
+import static com.android.server.wm.proto.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.proto.IdentifierProto.TITLE;
+import static com.android.server.wm.proto.IdentifierProto.USER_ID;
+
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -153,6 +157,7 @@
 import android.util.MergedConfiguration;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.AppTransitionAnimationSpec;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
@@ -1038,7 +1043,7 @@
             }
         } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
             activityType = ACTIVITY_TYPE_RECENTS;
-        } else if (options != null && options.getLaunchStackId() == ASSISTANT_STACK_ID
+        } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
                 && canLaunchAssistActivity(launchedFromPackage)) {
             activityType = ACTIVITY_TYPE_ASSISTANT;
         }
@@ -1062,6 +1067,11 @@
         return getStack() != null ? getStack().mStackId : INVALID_STACK_ID;
     }
 
+    ActivityDisplay getDisplay() {
+        final ActivityStack stack = getStack();
+        return stack != null ? stack.getDisplay() : null;
+    }
+
     boolean changeWindowTranslucency(boolean toOpaque) {
         if (fullscreen == toOpaque) {
             return false;
@@ -1127,10 +1137,12 @@
      * @return whether this activity supports split-screen multi-window and can be put in the docked
      *         stack.
      */
-    boolean supportsSplitScreen() {
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
         // An activity can not be docked even if it is considered resizeable because it only
         // supports picture-in-picture mode but has a non-resizeable resizeMode
-        return service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
+        return super.supportsSplitScreenWindowingMode()
+                && service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
     }
 
     /**
@@ -1157,8 +1169,15 @@
      *         can be put a secondary screen.
      */
     boolean canBeLaunchedOnDisplay(int displayId) {
+        final TaskRecord task = getTask();
+
+        // The resizeability of an Activity's parent task takes precendence over the ActivityInfo.
+        // This allows for a non resizable activity to be launched into a resizeable task.
+        final boolean resizeable =
+                task != null ? task.isResizeable() : supportsResizeableMultiWindow();
+
         return service.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
-                supportsResizeableMultiWindow(), launchedFromPid, launchedFromUid, info);
+                resizeable, launchedFromPid, launchedFromUid, info);
     }
 
     /**
@@ -1184,7 +1203,8 @@
 
         boolean isKeyguardLocked = service.isKeyguardLocked();
         boolean isCurrentAppLocked = service.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
-        boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+        final ActivityDisplay display = getDisplay();
+        boolean hasPinnedStack = display != null && display.hasPinnedStack();
         // Don't return early if !isNotLocked, since we want to throw an exception if the activity
         // is in an incorrect state
         boolean isNotLockedOrOnKeyguard = !isKeyguardLocked && !isCurrentAppLocked;
@@ -1530,8 +1550,9 @@
         if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) {
             // 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.
-            isVisible = mStackSupervisor.getStack(DOCKED_STACK_ID) != null
-                    || mStackSupervisor.isFocusedStack(getStack());
+            final ActivityDisplay display = getDisplay();
+            boolean hasSplitScreenStack = display != null && display.hasSplitScreenStack();
+            isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
         }
 
         return isVisible;
@@ -1934,7 +1955,7 @@
 
         return (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0
                 || (mStackSupervisor.isCurrentProfileLocked(userId)
-                && service.mUserController.isUserRunningLocked(userId, 0 /* flags */));
+                && service.mUserController.isUserRunning(userId, 0 /* flags */));
     }
 
     /**
@@ -2298,7 +2319,7 @@
         // be visible based on the stack, task, and lockscreen state and use that here instead. The
         // method should be based on the logic in ActivityStack.ensureActivitiesVisibleLocked().
         // Skip updating configuration for activity is a stack that shouldn't be visible.
-        if (stack.shouldBeVisible(null /* starting */) == STACK_INVISIBLE) {
+        if (!stack.shouldBeVisible(null /* starting */)) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                     "Skipping config check invisible stack: " + this);
             return true;
@@ -2770,4 +2791,25 @@
         stringName = sb.toString();
         return toString();
     }
+
+    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        proto.write(USER_ID, userId);
+        proto.write(TITLE, intent.getComponent().flattenToShortString());
+        proto.end(token);
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        writeIdentifierToProto(proto, IDENTIFIER);
+        proto.write(STATE, state.toString());
+        proto.write(VISIBLE, visible);
+        proto.write(FRONT_OF_TASK, frontOfTask);
+        if (app != null) {
+            proto.write(PROC_ID, app.pid);
+        }
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index a6a702f..1940ca2 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,19 +16,19 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
 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.HOME_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.getActivityTypeForStackId;
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
 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_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.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
@@ -37,6 +37,8 @@
 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;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_APP;
@@ -74,10 +76,16 @@
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
-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.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.am.proto.ActivityStackProto.BOUNDS;
+import static com.android.server.am.proto.ActivityStackProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityStackProto.DISPLAY_ID;
+import static com.android.server.am.proto.ActivityStackProto.FULLSCREEN;
+import static com.android.server.am.proto.ActivityStackProto.ID;
+import static com.android.server.am.proto.ActivityStackProto.RESUMED_ACTIVITY;
+import static com.android.server.am.proto.ActivityStackProto.TASKS;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -86,6 +94,7 @@
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN_BEHIND;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
+
 import static java.lang.Integer.MAX_VALUE;
 
 import android.app.Activity;
@@ -101,7 +110,6 @@
 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;
@@ -122,6 +130,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -231,11 +240,6 @@
         DESTROYED
     }
 
-    // Stack is not considered visible.
-    static final int STACK_INVISIBLE = 0;
-    // Stack is considered visible
-    static final int STACK_VISIBLE = 1;
-
     @VisibleForTesting
     /* The various modes for the method {@link #removeTask}. */
     // Task is being completely removed from all stacks in the system.
@@ -258,7 +262,6 @@
     final ActivityManagerService mService;
     private final WindowManagerService mWindowManager;
     T mWindowContainerController;
-    private final RecentTasks mRecentTasks;
 
     /**
      * The back history of all previous (and possibly still
@@ -341,9 +344,6 @@
     int mCurrentUser;
 
     final int mStackId;
-    /** The other stacks, in order, on the attached display. Updated at attach/detach time. */
-    // TODO: This list doesn't belong here...
-    ArrayList<ActivityStack> mStacks;
     /** The attached Display's unique identifier, or -1 if detached */
     int mDisplayId;
 
@@ -452,49 +452,35 @@
         return count;
     }
 
-    ActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
-            ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
+    ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+            int windowingMode, int activityType, boolean onTop) {
         mStackSupervisor = supervisor;
         mService = supervisor.mService;
         mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
         mWindowManager = mService.mWindowManager;
         mStackId = stackId;
-        mCurrentUser = mService.mUserController.getCurrentUserIdLocked();
-        mRecentTasks = recentTasks;
+        mCurrentUser = mService.mUserController.getCurrentUserId();
         mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
                 ? new LaunchingTaskPositioner() : null;
         mTmpRect2.setEmpty();
-        updateOverrideConfiguration();
+        setWindowingMode(windowingMode);
+        setActivityType(activityType);
         mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
                 mTmpRect2);
-        mStackSupervisor.mStacks.put(mStackId, this);
         postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
     }
 
     T createStackWindowController(int displayId, boolean onTop, Rect outBounds) {
-        return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds);
+        return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds,
+                mStackSupervisor.mWindowManager);
     }
 
     T getWindowContainerController() {
         return mWindowContainerController;
     }
 
-    // TODO: Not needed once we are no longer using stack ids as the override config. can be passed
-    // in.
-    private void updateOverrideConfiguration() {
-        final int windowingMode = getWindowingModeForStackId(
-                mStackId, mStackSupervisor.getStack(DOCKED_STACK_ID) != null);
-        if (windowingMode != WINDOWING_MODE_UNDEFINED) {
-            setWindowingMode(windowingMode);
-        }
-        final int activityType = getActivityTypeForStackId(mStackId);
-        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
-            setActivityType(activityType);
-        }
-    }
-
     /** Adds the stack to specified display and calls WindowManager to do the same. */
-    void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+    void reparent(ActivityDisplay activityDisplay, boolean onTop) {
         removeFromDisplay();
         mTmpRect2.setEmpty();
         postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -512,10 +498,8 @@
      * @param activityDisplay New display to which this stack was attached.
      * @param bounds Updated bounds.
      */
-    private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
-            Rect bounds, boolean onTop) {
+    private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
         mDisplayId = activityDisplay.mDisplayId;
-        mStacks = activityDisplay.mStacks;
         mBounds = bounds != null ? new Rect(bounds) : null;
         mFullscreen = mBounds == null;
         if (mTaskPositioner != null) {
@@ -524,7 +508,7 @@
         }
         onParentChanged();
 
-        activityDisplay.attachStack(this, findStackInsertIndex(onTop));
+        activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
         if (mStackId == DOCKED_STACK_ID) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
@@ -538,42 +522,36 @@
      * either destroyed completely or re-parented.
      */
     private void removeFromDisplay() {
-        final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
-        if (display != null) {
-            display.detachStack(this);
-        }
-        mDisplayId = INVALID_DISPLAY;
-        mStacks = null;
-        if (mTaskPositioner != null) {
-            mTaskPositioner.reset();
-        }
-        if (mStackId == DOCKED_STACK_ID) {
+        if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // If we removed a docked stack we want to resize it so it resizes all other stacks
             // in the system to fullscreen.
             mStackSupervisor.resizeDockedStackLocked(
                     null, null, null, null, null, PRESERVE_WINDOWS);
         }
+        final ActivityDisplay display = getDisplay();
+        if (display != null) {
+            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. */
     void remove() {
         removeFromDisplay();
-        mStackSupervisor.mStacks.remove(mStackId);
         mWindowContainerController.removeContainer();
         mWindowContainerController = null;
         onParentChanged();
     }
 
-    ActivityStackSupervisor.ActivityDisplay getDisplay() {
+    ActivityDisplay getDisplay() {
         return mStackSupervisor.getActivityDisplay(mDisplayId);
     }
 
-    void getDisplaySize(Point out) {
-        getDisplay().mDisplay.getSize(out);
-    }
-
     /**
-     * @see ActivityStack.getStackDockedModeBounds(Rect, Rect, Rect, boolean)
+     * @see #getStackDockedModeBounds(Rect, Rect, Rect, boolean)
      */
     void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds,
             Rect outTempTaskBounds, boolean ignoreVisibility) {
@@ -837,7 +815,7 @@
     }
 
     final boolean isHomeOrRecentsStack() {
-        return StackId.isHomeOrRecentsStack(mStackId);
+        return isActivityTypeHome() || isActivityTypeRecents();
     }
 
     final boolean isDockedStack() {
@@ -849,7 +827,7 @@
     }
 
     final boolean isOnHomeDisplay() {
-        return isAttached() && mDisplayId == DEFAULT_DISPLAY;
+        return mDisplayId == DEFAULT_DISPLAY;
     }
 
     void moveToFront(String reason) {
@@ -865,8 +843,7 @@
             return;
         }
 
-        mStacks.remove(this);
-        mStacks.add(findStackInsertIndex(ON_TOP), this);
+        getDisplay().positionChildAtTop(this);
         mStackSupervisor.setFocusStackUnchecked(reason, this);
         if (task != null) {
             insertTaskAtTop(task, null);
@@ -880,45 +857,6 @@
         }
     }
 
-    /**
-     * @param task If non-null, the task will be moved to the back of the stack.
-     * */
-    private void moveToBack(TaskRecord task) {
-        if (!isAttached()) {
-            return;
-        }
-
-        mStacks.remove(this);
-        mStacks.add(0, this);
-
-        if (task != null) {
-            mTaskHistory.remove(task);
-            mTaskHistory.add(0, task);
-            updateTaskMovement(task, false);
-            mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
-        }
-    }
-
-    /**
-     * @return the index to insert a new stack into, taking the always-on-top stacks into account.
-     */
-    private int findStackInsertIndex(boolean onTop) {
-        if (onTop) {
-            int addIndex = mStacks.size();
-            if (addIndex > 0) {
-                final ActivityStack topStack = mStacks.get(addIndex - 1);
-                if (topStack.getWindowConfiguration().isAlwaysOnTop()
-                        && topStack != this) {
-                    // If the top stack is always on top, we move this stack just below it.
-                    addIndex--;
-                }
-            }
-            return addIndex;
-        } else {
-            return 0;
-        }
-    }
-
     boolean isFocusable() {
         if (getWindowConfiguration().canReceiveKeys()) {
             return true;
@@ -930,7 +868,7 @@
     }
 
     final boolean isAttached() {
-        return mStacks != null;
+        return getParent() != null;
     }
 
     /**
@@ -1105,14 +1043,6 @@
                 "Launch completed; removing icicle of " + r.icicle);
     }
 
-    void addRecentActivityLocked(ActivityRecord r) {
-        if (r != null) {
-            final TaskRecord task = r.getTask();
-            mRecentTasks.addLocked(task);
-            task.touchActiveTime();
-        }
-    }
-
     private void startLaunchTraces(String packageName) {
         if (mFullyDrawnStartTime != 0)  {
             Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
@@ -1527,7 +1457,7 @@
         // focus). Also if there is an active pinned stack - we always want to notify it about
         // task stack changes, because its positioning may depend on it.
         if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause
-                || mService.mStackSupervisor.getStack(PINNED_STACK_ID) != null) {
+                || getDisplay().hasPinnedStack()) {
             mService.mTaskChangeNotificationController.notifyTaskStackChanged();
             mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
         }
@@ -1559,54 +1489,6 @@
         }
     }
 
-    // Find the first visible activity above the passed activity and if it is translucent return it
-    // otherwise return null;
-    ActivityRecord findNextTranslucentActivity(ActivityRecord r) {
-        TaskRecord task = r.getTask();
-        if (task == null) {
-            return null;
-        }
-
-        final ActivityStack stack = task.getStack();
-        if (stack == null) {
-            return null;
-        }
-
-        int stackNdx = mStacks.indexOf(stack);
-
-        ArrayList<TaskRecord> tasks = stack.mTaskHistory;
-        int taskNdx = tasks.indexOf(task);
-
-        ArrayList<ActivityRecord> activities = task.mActivities;
-        int activityNdx = activities.indexOf(r) + 1;
-
-        final int numStacks = mStacks.size();
-        while (stackNdx < numStacks) {
-            final ActivityStack historyStack = mStacks.get(stackNdx);
-            tasks = historyStack.mTaskHistory;
-            final int numTasks = tasks.size();
-            while (taskNdx < numTasks) {
-                final TaskRecord currentTask = tasks.get(taskNdx);
-                activities = currentTask.mActivities;
-                final int numActivities = activities.size();
-                while (activityNdx < numActivities) {
-                    final ActivityRecord activity = activities.get(activityNdx);
-                    if (!activity.finishing) {
-                        return historyStack.mFullscreen
-                                && currentTask.mFullscreen && activity.fullscreen ? null : activity;
-                    }
-                    ++activityNdx;
-                }
-                activityNdx = 0;
-                ++taskNdx;
-            }
-            taskNdx = 0;
-            ++stackNdx;
-        }
-
-        return null;
-    }
-
     /** Returns true if the stack contains a fullscreen task. */
     private boolean hasFullscreenTask() {
         for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
@@ -1650,9 +1532,11 @@
                     return false;
                 }
 
+                final ActivityStack stackBehind = mStackSupervisor.getStack(stackBehindId);
+                final boolean stackBehindHomeOrRecent = stackBehind != null
+                        && stackBehind.isHomeOrRecentsStack();
                 if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
-                        && !StackId.isHomeOrRecentsStack(stackBehindId)
-                        && !isActivityTypeAssistant()) {
+                        && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) {
                     // Stack isn't translucent if it's top activity should have the home stack
                     // behind it and the stack currently behind it isn't the home or recents stack
                     // or the assistant stack.
@@ -1670,119 +1554,146 @@
     }
 
     /**
-     * Returns what the stack visibility should be: {@link #STACK_INVISIBLE} or
-     * {@link #STACK_VISIBLE}.
+     * Returns true if the stack should be visible.
      *
      * @param starting The currently starting activity or null if there is none.
      */
-    int shouldBeVisible(ActivityRecord starting) {
+    boolean shouldBeVisible(ActivityRecord starting) {
         if (!isAttached() || mForceHidden) {
-            return STACK_INVISIBLE;
+            return false;
         }
 
         if (mStackSupervisor.isFrontStackOnDisplay(this) || mStackSupervisor.isFocusedStack(this)) {
-            return STACK_VISIBLE;
+            return true;
         }
 
-        final int stackIndex = mStacks.indexOf(this);
+        final ActivityDisplay display = getDisplay();
+        final ArrayList<ActivityStack> displayStacks = display.mStacks;
+        final int stackIndex = displayStacks.indexOf(this);
 
-        if (stackIndex == mStacks.size() - 1) {
+        if (stackIndex == displayStacks.size() - 1) {
             Slog.wtf(TAG,
                     "Stack=" + this + " isn't front stack but is at the top of the stack list");
-            return STACK_INVISIBLE;
+            return false;
         }
 
         // 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 int windowingMode = getWindowingMode();
+        final int activityType = getActivityType();
 
-        if (mStackId == DOCKED_STACK_ID) {
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // 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)) ? STACK_VISIBLE
-                        : STACK_INVISIBLE;
+                return topStack.isStackTranslucent(starting, DOCKED_STACK_ID);
             }
-            return STACK_VISIBLE;
+            return true;
         }
 
         // Set home stack to invisible when it is below but not immediately below the docked stack
         // 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 (mStackId == HOME_STACK_ID) {
-            int dockedStackIndex = mStacks.indexOf(mStackSupervisor.getStack(DOCKED_STACK_ID));
+        if (activityType == ACTIVITY_TYPE_HOME) {
+            final ActivityStack splitScreenStack = display.getStack(
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+            int dockedStackIndex = displayStacks.indexOf(splitScreenStack);
             if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
-                return STACK_INVISIBLE;
+                return false;
             }
         }
 
         // Find the first stack behind front stack that actually got something visible.
-        int stackBehindTopIndex = mStacks.indexOf(topStack) - 1;
+        int stackBehindTopIndex = displayStacks.indexOf(topStack) - 1;
         while (stackBehindTopIndex >= 0 &&
-                mStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
+                displayStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
             stackBehindTopIndex--;
         }
-        final int stackBehindTopId = (stackBehindTopIndex >= 0)
-                ? mStacks.get(stackBehindTopIndex).mStackId : INVALID_STACK_ID;
+        final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
+                ? displayStacks.get(stackBehindTopIndex) : null;
+        int stackBehindTopId = INVALID_STACK_ID;
+        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 (topStackId == DOCKED_STACK_ID || alwaysOnTop) {
+        if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
             if (stackIndex == stackBehindTopIndex) {
                 // Stacks directly behind the docked or pinned stack are always visible.
-                return STACK_VISIBLE;
+                return true;
             } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
                 // Otherwise, this stack can also be visible if it is directly behind a docked stack
                 // or translucent assistant stack behind an always-on-top top-most stack
-                if (stackBehindTopId == DOCKED_STACK_ID) {
-                    return STACK_VISIBLE;
-                } else if (stackBehindTopId == ASSISTANT_STACK_ID) {
-                    return mStacks.get(stackBehindTopIndex).isStackTranslucent(starting, mStackId)
-                            ? STACK_VISIBLE : STACK_INVISIBLE;
+                if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                    return true;
+                } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
+                    return displayStacks.get(stackBehindTopIndex).isStackTranslucent(
+                            starting, mStackId);
                 }
             }
         }
 
-        if (StackId.isBackdropToTranslucentActivity(topStackId)
+        if (topStack.isBackdropToTranslucentActivity()
                 && topStack.isStackTranslucent(starting, stackBehindTopId)) {
             // 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
             if (stackIndex == stackBehindTopIndex) {
-                return STACK_VISIBLE;
+                return true;
             }
             if (stackBehindTopIndex >= 0) {
-                if ((stackBehindTopId == DOCKED_STACK_ID
-                        || stackBehindTopId == PINNED_STACK_ID)
+                if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                        || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED)
                         && stackIndex == (stackBehindTopIndex - 1)) {
                     // The stack behind the docked or pinned stack is also visible so we can have a
                     // complete backdrop to the translucent activity when the docked stack is up.
-                    return STACK_VISIBLE;
+                    return true;
                 }
             }
         }
 
-        if (StackId.isStaticStack(mStackId)) {
+        if (StackId.isStaticStack(mStackId)
+                || isHomeOrRecentsStack() || isActivityTypeAssistant()) {
             // Visibility of any static stack should have been determined by the conditions above.
-            return STACK_INVISIBLE;
+            return false;
         }
 
-        for (int i = stackIndex + 1; i < mStacks.size(); i++) {
-            final ActivityStack stack = mStacks.get(i);
+        for (int i = stackIndex + 1; i < displayStacks.size(); i++) {
+            final ActivityStack stack = displayStacks.get(i);
 
             if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
                 continue;
             }
 
-            if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
+            if (!stack.isDynamicStacksVisibleBehindAllowed()) {
                 // These stacks can't have any dynamic stacks visible behind them.
-                return STACK_INVISIBLE;
+                return false;
             }
 
             if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) {
-                return STACK_INVISIBLE;
+                return false;
             }
         }
 
-        return STACK_VISIBLE;
+        return true;
+    }
+
+    private boolean isBackdropToTranslucentActivity() {
+        if (isActivityTypeAssistant()) {
+            return true;
+        }
+        final int windowingMode = getWindowingMode();
+        return windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+    }
+
+    private boolean isDynamicStacksVisibleBehindAllowed() {
+        return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED;
     }
 
     final int rankTaskLayers(int baseLayer) {
@@ -1819,12 +1730,10 @@
             // If the top activity is not fullscreen, then we need to
             // make sure any activities under it are now visible.
             boolean aboveTop = top != null;
-            final int stackVisibility = shouldBeVisible(starting);
-            final boolean stackInvisible = stackVisibility != STACK_VISIBLE;
-            boolean behindFullscreenActivity = stackInvisible;
+            final boolean stackShouldBeVisible = shouldBeVisible(starting);
+            boolean behindFullscreenActivity = !stackShouldBeVisible;
             boolean resumeNextActivity = mStackSupervisor.isFocusedStack(this)
                     && (isInStackLocked(starting) == null);
-            boolean behindTranslucentActivity = false;
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
                 final TaskRecord task = mTaskHistory.get(taskNdx);
                 final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1848,11 +1757,8 @@
                     final boolean reallyVisible = checkKeyguardVisibility(r,
                             visibleIgnoringKeyguard, isTop);
                     if (visibleIgnoringKeyguard) {
-                        behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
+                        behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
                                 behindFullscreenActivity, task, r);
-                        if (behindFullscreenActivity && !r.fullscreen) {
-                            behindTranslucentActivity = true;
-                        }
                     }
                     if (reallyVisible) {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
@@ -1888,10 +1794,10 @@
                         configChanges |= r.configChangeFlags;
                     } else {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
-                                + " finishing=" + r.finishing + " state=" + r.state + " stackInvisible="
-                                + stackInvisible + " behindFullscreenActivity="
-                                + behindFullscreenActivity + " mLaunchTaskBehind="
-                                + r.mLaunchTaskBehind);
+                                + " finishing=" + r.finishing + " state=" + r.state
+                                + " stackShouldBeVisible=" + stackShouldBeVisible
+                                + " behindFullscreenActivity=" + behindFullscreenActivity
+                                + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
                         makeInvisible(r);
                     }
                 }
@@ -1899,10 +1805,10 @@
                     // 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.
-                    behindFullscreenActivity = stackVisibility == STACK_INVISIBLE;
-                } else if (mStackId == HOME_STACK_ID) {
+                    behindFullscreenActivity = !stackShouldBeVisible;
+                } else if (isActivityTypeHome()) {
                     if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task
-                            + " stackInvisible=" + stackInvisible
+                            + " stackShouldBeVisible=" + stackShouldBeVisible
                             + " behindFullscreenActivity=" + behindFullscreenActivity);
                     // No other task in the home stack should be visible behind the home activity.
                     // Home activities is usually a translucent activity with the wallpaper behind
@@ -1962,7 +1868,8 @@
     boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible,
             boolean isTop) {
         final boolean isInPinnedStack = r.getStack().getStackId() == PINNED_STACK_ID;
-        final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing();
+        final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
+                mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
         final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
         final boolean showWhenLocked = r.canShowWhenLocked() && !isInPinnedStack;
         final boolean dismissKeyguard = r.hasDismissKeyguardWindows();
@@ -2002,7 +1909,7 @@
      * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
      */
     private boolean canShowWithInsecureKeyguard() {
-        final ActivityStackSupervisor.ActivityDisplay activityDisplay = getDisplay();
+        final ActivityDisplay activityDisplay = getDisplay();
         if (activityDisplay == null) {
             throw new IllegalStateException("Stack is not attached to any display, stackId="
                     + mStackId);
@@ -2182,7 +2089,7 @@
         // activities as we need to display their starting window until they are done initializing.
         boolean behindFullscreenActivity = false;
 
-        if (shouldBeVisible(null) == STACK_INVISIBLE) {
+        if (!shouldBeVisible(null)) {
             // The stack is not visible, so no activity in it should be displaying a starting
             // window. Mark all activities below top and behind fullscreen.
             aboveTop = false;
@@ -2258,9 +2165,7 @@
         mResumedActivity = r;
         r.state = ActivityState.RESUMED;
         mService.setResumedActivityUncheckLocked(r, reason);
-        final TaskRecord task = r.getTask();
-        task.touchActiveTime();
-        mRecentTasks.addLocked(task);
+        mStackSupervisor.addRecentActivity(r);
     }
 
     private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
@@ -2543,128 +2448,139 @@
                     || (lastStack.mLastPausedActivity != null
                     && !lastStack.mLastPausedActivity.fullscreen));
 
-            // This activity is now becoming visible.
-            if (!next.visible || next.stopped || lastActivityTranslucent) {
-                next.setVisibility(true);
-            }
-
-            // schedule launch ticks to collect information about slow apps.
-            next.startLaunchTickingLocked();
-
-            ActivityRecord lastResumedActivity =
-                    lastStack == null ? null :lastStack.mResumedActivity;
-            ActivityState lastState = next.state;
-
-            mService.updateCpuStats();
-
-            if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)");
-
-            setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
-
-            mService.updateLruProcessLocked(next.app, true, null);
-            updateLRUListLocked(next);
-            mService.updateOomAdjLocked();
-
-            // Have the window manager re-evaluate the orientation of
-            // the screen based on the new activity order.
-            boolean notUpdated = true;
-            if (mStackSupervisor.isFocusedStack(this)) {
-
-                // We have special rotation behavior when Keyguard is locked. Make sure all activity
-                // visibilities are set correctly as well as the transition is updated if needed to
-                // get the correct rotation behavior.
-                // TODO: Remove this once visibilities are set correctly immediately when starting
-                // an activity.
-                if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
-                    mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
-                            0 /* configChanges */, false /* preserveWindows */);
-                }
-                final Configuration config = mWindowManager.updateOrientationFromAppTokens(
-                        mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
-                        next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
-                if (config != null) {
-                    next.frozenBeforeDestroy = true;
-                }
-                notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
-                        false /* deferResume */, mDisplayId);
-            }
-
-            if (notUpdated) {
-                // The configuration update wasn't able to keep the existing
-                // instance of the activity, and instead started a new one.
-                // We should be all done, but let's just make sure our activity
-                // is still at the top and schedule another run if something
-                // weird happened.
-                ActivityRecord nextNext = topRunningActivityLocked();
-                if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
-                        "Activity config changed during resume: " + next
-                        + ", new next: " + nextNext);
-                if (nextNext != next) {
-                    // Do over!
-                    mStackSupervisor.scheduleResumeTopActivities();
-                }
-                if (!next.visible || next.stopped) {
+            // The contained logic must be synchronized, since we are both changing the visibility
+            // and updating the {@link Configuration}. {@link ActivityRecord#setVisibility} will
+            // ultimately cause the client code to schedule a layout. Since layouts retrieve the
+            // current {@link Configuration}, we must ensure that the below code updates it before
+            // the layout can occur.
+            synchronized(mWindowManager.getWindowManagerLock()) {
+                // This activity is now becoming visible.
+                if (!next.visible || next.stopped || lastActivityTranslucent) {
                     next.setVisibility(true);
                 }
-                next.completeResumeLocked();
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
-            }
 
-            try {
-                // Deliver all pending results.
-                ArrayList<ResultInfo> a = next.results;
-                if (a != null) {
-                    final int N = a.size();
-                    if (!next.finishing && N > 0) {
-                        if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
-                                "Delivering results to " + next + ": " + a);
-                        next.app.thread.scheduleSendResult(next.appToken, a);
+                // schedule launch ticks to collect information about slow apps.
+                next.startLaunchTickingLocked();
+
+                ActivityRecord lastResumedActivity =
+                        lastStack == null ? null :lastStack.mResumedActivity;
+                ActivityState lastState = next.state;
+
+                mService.updateCpuStats();
+
+                if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next
+                        + " (in existing)");
+
+                setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
+
+                mService.updateLruProcessLocked(next.app, true, null);
+                updateLRUListLocked(next);
+                mService.updateOomAdjLocked();
+
+                // Have the window manager re-evaluate the orientation of
+                // the screen based on the new activity order.
+                boolean notUpdated = true;
+
+                if (mStackSupervisor.isFocusedStack(this)) {
+
+                    // We have special rotation behavior when Keyguard is locked. Make sure all
+                    // activity visibilities are set correctly as well as the transition is updated
+                    // if needed to get the correct rotation behavior.
+                    // TODO: Remove this once visibilities are set correctly immediately when
+                    // starting an activity.
+                    if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
+                        mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
+                                0 /* configChanges */, false /* preserveWindows */);
                     }
+                    final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                            mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+                            next.mayFreezeScreenLocked(next.app) ? next.appToken : null,
+                                    mDisplayId);
+                    if (config != null) {
+                        next.frozenBeforeDestroy = true;
+                    }
+                    notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+                            false /* deferResume */, mDisplayId);
                 }
 
-                if (next.newIntents != null) {
-                    next.app.thread.scheduleNewIntent(
-                            next.newIntents, next.appToken, false /* andPause */);
+                if (notUpdated) {
+                    // The configuration update wasn't able to keep the existing
+                    // instance of the activity, and instead started a new one.
+                    // We should be all done, but let's just make sure our activity
+                    // is still at the top and schedule another run if something
+                    // weird happened.
+                    ActivityRecord nextNext = topRunningActivityLocked();
+                    if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
+                            "Activity config changed during resume: " + next
+                                    + ", new next: " + nextNext);
+                    if (nextNext != next) {
+                        // Do over!
+                        mStackSupervisor.scheduleResumeTopActivities();
+                    }
+                    if (!next.visible || next.stopped) {
+                        next.setVisibility(true);
+                    }
+                    next.completeResumeLocked();
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
 
-                // Well the app will no longer be stopped.
-                // Clear app token stopped state in window manager if needed.
-                next.notifyAppResumed(next.stopped);
+                try {
+                    // Deliver all pending results.
+                    ArrayList<ResultInfo> a = next.results;
+                    if (a != null) {
+                        final int N = a.size();
+                        if (!next.finishing && N > 0) {
+                            if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+                                    "Delivering results to " + next + ": " + a);
+                            next.app.thread.scheduleSendResult(next.appToken, a);
+                        }
+                    }
 
-                EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
-                        System.identityHashCode(next), next.getTask().taskId,
-                        next.shortComponentName);
+                    if (next.newIntents != null) {
+                        next.app.thread.scheduleNewIntent(
+                                next.newIntents, next.appToken, false /* andPause */);
+                    }
 
-                next.sleeping = false;
-                mService.showUnsupportedZoomDialogIfNeededLocked(next);
-                mService.showAskCompatModeDialogLocked(next);
-                next.app.pendingUiClean = true;
-                next.app.forceProcessStateUpTo(mService.mTopProcessState);
-                next.clearOptionsLocked();
-                next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
-                        mService.isNextTransitionForward(), resumeAnimOptions);
+                    // Well the app will no longer be stopped.
+                    // Clear app token stopped state in window manager if needed.
+                    next.notifyAppResumed(next.stopped);
 
-                if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next);
-            } catch (Exception e) {
-                // Whoops, need to restart this activity!
-                if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
-                        + lastState + ": " + next);
-                next.state = lastState;
-                if (lastStack != null) {
-                    lastStack.mResumedActivity = lastResumedActivity;
+                    EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
+                            System.identityHashCode(next), next.getTask().taskId,
+                            next.shortComponentName);
+
+                    next.sleeping = false;
+                    mService.showUnsupportedZoomDialogIfNeededLocked(next);
+                    mService.showAskCompatModeDialogLocked(next);
+                    next.app.pendingUiClean = true;
+                    next.app.forceProcessStateUpTo(mService.mTopProcessState);
+                    next.clearOptionsLocked();
+                    next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
+                            mService.isNextTransitionForward(), resumeAnimOptions);
+
+                    if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+                            + next);
+                } catch (Exception e) {
+                    // Whoops, need to restart this activity!
+                    if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
+                            + lastState + ": " + next);
+                    next.state = lastState;
+                    if (lastStack != null) {
+                        lastStack.mResumedActivity = lastResumedActivity;
+                    }
+                    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)) {
+                        next.showStartingWindow(null /* prev */, false /* newTask */,
+                                false /* taskSwitch */);
+                    }
+                    mStackSupervisor.startSpecificActivityLocked(next, true, false);
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
-                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)) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwitch */);
-                }
-                mStackSupervisor.startSpecificActivityLocked(next, true, false);
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
             }
 
             // From this point on, if something goes wrong there is no way
@@ -2989,9 +2905,9 @@
             // Ensure that we do not trigger entering PiP an activity on the pinned stack
             return false;
         }
-        final int targetStackId = toFrontTask != null ? toFrontTask.getStackId()
-                : toFrontActivity.getStackId();
-        if (targetStackId == ASSISTANT_STACK_ID) {
+        final ActivityStack targetStack = toFrontTask != null
+                ? toFrontTask.getStack() : toFrontActivity.getStack();
+        if (targetStack != null && targetStack.isActivityTypeAssistant()) {
             // Ensure the task/activity being brought forward is not the assistant
             return false;
         }
@@ -4548,7 +4464,7 @@
         // Don't refocus if invisible to current user
         final ActivityRecord top = tr.getTopActivity();
         if (top == null || !top.okToShowLocked()) {
-            addRecentActivityLocked(top);
+            mStackSupervisor.addRecentActivity(top);
             ActivityOptions.abort(options);
             return;
         }
@@ -4601,7 +4517,7 @@
         Slog.i(TAG, "moveTaskToBack: " + tr);
 
         // If the task is locked, then show the lock task toast
-        if (!mService.mLockTaskController.checkLockedTask(tr)) {
+        if (mService.mLockTaskController.checkLockedTask(tr)) {
             return false;
         }
 
@@ -4982,8 +4898,9 @@
             }
             ci.numActivities = numActivities;
             ci.numRunning = numRunning;
-            ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen();
+            ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
             ci.resizeMode = task.mResizeMode;
+            ci.configuration.setTo(task.getConfiguration());
             list.add(ci);
         }
     }
@@ -5152,8 +5069,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.
-                mRecentTasks.remove(task);
-                task.removedFromRecents();
+                mStackSupervisor.removeTaskFromRecents(task);
             }
 
             task.removeWindowContainer();
@@ -5170,11 +5086,10 @@
                     mStackSupervisor.moveHomeStackToFront(myReason);
                 }
             }
-            if (mStacks != null) {
-                mStacks.remove(this);
-                mStacks.add(0, this);
+            if (isAttached()) {
+                getDisplay().positionChildAtBottom(this);
             }
-            if (!isHomeOrRecentsStack()) {
+            if (!isActivityTypeHome()) {
                 remove();
             }
         }
@@ -5194,8 +5109,8 @@
                 voiceInteractor);
         // add the task to stack first, mTaskPositioner might need the stack association
         addTask(task, toTop, "createTaskRecord");
-        final boolean isLockscreenShown =
-                mService.mStackSupervisor.mKeyguardController.isKeyguardShowing();
+        final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
+                .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
         if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable()
                 && !isLockscreenShown) {
             task.updateOverrideConfiguration(mBounds);
@@ -5349,11 +5264,30 @@
     }
 
     boolean shouldSleepActivities() {
-        final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
+        final ActivityDisplay display = getDisplay();
         return display != null ? display.isSleeping() : mService.isSleepingLocked();
     }
 
     boolean shouldSleepOrShutDownActivities() {
         return shouldSleepActivities() || mService.isShuttingDownLocked();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        proto.write(ID, mStackId);
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            task.writeToProto(proto, TASKS);
+        }
+        if (mResumedActivity != null) {
+            mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+        }
+        proto.write(DISPLAY_ID, mDisplayId);
+        if (mBounds != null) {
+            mBounds.writeToProto(proto, BOUNDS);
+        }
+        proto.write(FULLSCREEN, mFullscreen);
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index fe28956..da2827a 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -23,30 +23,29 @@
 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.FIRST_STATIC_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.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_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;
 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_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.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.FLAG_PRIVATE;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 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;
@@ -81,19 +80,24 @@
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStack.STACK_VISIBLE;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 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.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;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -106,6 +110,7 @@
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.WaitResult;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -147,6 +152,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -166,7 +172,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -375,15 +380,11 @@
      * They are used by components that may hide and block interaction with underlying
      * activities.
      */
-    final ArrayList<SleepToken> mSleepTokens = new ArrayList<SleepToken>();
+    final ArrayList<SleepToken> mSleepTokens = new ArrayList<>();
 
     /** Stack id of the front stack when user switched, indexed by userId. */
     SparseIntArray mUserStackInFront = new SparseIntArray(2);
 
-    // TODO: Add listener for removal of references.
-    /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
-    SparseArray<ActivityStack> mStacks = new SparseArray<>();
-
     // TODO: There should be an ActivityDisplayController coordinating am/wm interaction.
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -602,16 +603,13 @@
             Display[] displays = mDisplayManager.getDisplays();
             for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) {
                 final int displayId = displays[displayNdx].getDisplayId();
-                ActivityDisplay activityDisplay = new ActivityDisplay(displayId);
-                if (activityDisplay.mDisplay == null) {
-                    throw new IllegalStateException("Default Display does not exist");
-                }
+                ActivityDisplay activityDisplay = new ActivityDisplay(this, displayId);
                 mActivityDisplays.put(displayId, activityDisplay);
                 calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
             }
 
-            mHomeStack = mFocusedStack = mLastFocusedStack =
-                    getStack(HOME_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+            mHomeStack = mFocusedStack = mLastFocusedStack = getDefaultDisplay().getOrCreateStack(
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
 
             mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         }
@@ -667,7 +665,8 @@
     }
 
     void moveRecentsStackToFront(String reason) {
-        final ActivityStack recentsStack = getStack(RECENTS_STACK_ID);
+        final ActivityStack recentsStack = getDefaultDisplay().getStack(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
         if (recentsStack != null) {
             recentsStack.moveToFront(reason);
         }
@@ -708,24 +707,26 @@
     }
 
     TaskRecord anyTaskForIdLocked(int id) {
-        return anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
-                INVALID_STACK_ID);
+        return anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE);
+    }
+
+    TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode) {
+        return anyTaskForIdLocked(id, matchMode, null);
     }
 
     /**
      * Returns a {@link TaskRecord} for the input id if available. {@code null} otherwise.
      * @param id Id of the task we would like returned.
      * @param matchMode The mode to match the given task id in.
-     * @param stackId The stack to restore the task to (default launch stack will be used if
-     *                stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}). Only
-     *                valid if the matchMode is
-     *                {@link #MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE}.
+     * @param aOptions The activity options to use for restoration. Can be null.
      */
-    TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode, int stackId) {
+    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
-        if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE &&
-                stackId != INVALID_STACK_ID) {
-            throw new IllegalArgumentException("Should not specify stackId for non-restore lookup");
+        // TODO: Don't really know if this is needed...
+        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");
         }
 
         int numDisplays = mActivityDisplays.size();
@@ -763,7 +764,7 @@
         }
 
         // Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
-        if (!restoreRecentTaskLocked(task, stackId)) {
+        if (!restoreRecentTaskLocked(task, aOptions)) {
             if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
                     "Couldn't restore task id=" + id + " found in recents");
             return null;
@@ -858,8 +859,8 @@
         // 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)
-                || anyTaskForIdLocked(candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
-                        INVALID_STACK_ID) != null) {
+                || anyTaskForIdLocked(
+                        candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
             candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);
             if (candidateTaskId == currentTaskId) {
                 // Something wrong!
@@ -1152,8 +1153,7 @@
 
     void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) {
         // Gather all of the running tasks for each stack into runningTaskLists.
-        ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists =
-                new ArrayList<ArrayList<RunningTaskInfo>>();
+        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;
@@ -1235,7 +1235,7 @@
         synchronized (mService) {
             return mService.getPackageManagerInternalLocked().resolveIntent(intent, resolvedType,
                     PackageManager.MATCH_INSTANT | PackageManager.MATCH_DEFAULT_ONLY | flags
-                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
+                    | ActivityManagerService.STOCK_PM_FLAGS, userId, true);
         }
     }
 
@@ -1639,6 +1639,9 @@
             return true;
         }
 
+        // Check if caller is already present on display
+        final boolean uidPresentOnDisplay = activityDisplay.isUidPresent(callingUid);
+
         final int displayOwnerUid = activityDisplay.mDisplay.getOwnerUid();
         if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
                 && displayOwnerUid != aInfo.applicationInfo.uid) {
@@ -1651,7 +1654,7 @@
             }
             // Check if the caller is allowed to embed activities from other apps.
             if (mService.checkPermission(ACTIVITY_EMBEDDING, callingPid, callingUid)
-                    == PERMISSION_DENIED) {
+                    == PERMISSION_DENIED && !uidPresentOnDisplay) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
                         + " disallow activity embedding without permission.");
                 return false;
@@ -1672,8 +1675,7 @@
             return true;
         }
 
-        // Check if caller is present on display
-        if (activityDisplay.isUidPresent(callingUid)) {
+        if (uidPresentOnDisplay) {
             if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
                     + " allow launch for caller present on the display");
             return true;
@@ -1953,7 +1955,7 @@
      */
     void updateUserStackLocked(int userId, ActivityStack stack) {
         if (userId != mCurrentUser) {
-            mUserStackInFront.put(userId, stack != null ? stack.getStackId() : HOME_STACK_ID);
+            mUserStackInFront.put(userId, stack != null ? stack.getStackId() : mHomeStack.mStackId);
         }
     }
 
@@ -2083,38 +2085,35 @@
             // we'll just indicate that this task returns to the home task.
             task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
         }
-        ActivityStack currentStack = task.getStack();
+        final ActivityStack currentStack = task.getStack();
         if (currentStack == null) {
             Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
                     + task + " to front. Stack is null");
             return;
         }
 
-        if (task.isResizeable() && options != null) {
-            int stackId = options.getLaunchStackId();
-            if (canUseActivityOptionsLaunchBounds(options, stackId)) {
-                final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
-                task.updateOverrideConfiguration(bounds);
-                if (stackId == INVALID_STACK_ID) {
-                    stackId = task.getLaunchStackId();
-                }
-                if (stackId != currentStack.mStackId) {
-                    task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
-                            DEFER_RESUME, "findTaskToMoveToFrontLocked");
-                    stackId = currentStack.mStackId;
-                    // moveTaskToStackUncheckedLocked() should already placed the task on top,
-                    // still need moveTaskToFrontLocked() below for any transition settings.
-                }
-                if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                    resizeStackLocked(stackId, bounds,
-                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                            !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME);
-                } else {
-                    // WM resizeTask must be done after the task is moved to the correct stack,
-                    // because Task's setBounds() also updates dim layer's bounds, but that has
-                    // dependency on the stack.
-                    task.resizeWindowContainer();
-                }
+        if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
+            final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+            task.updateOverrideConfiguration(bounds);
+
+            ActivityStack stack = getLaunchStack(null, options, task, ON_TOP);
+
+            if (stack != currentStack) {
+                task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
+                        "findTaskToMoveToFrontLocked");
+                stack = currentStack;
+                // 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 */,
+                        null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
+                        true /* allowResizeInDockedMode */, !DEFER_RESUME);
+            } else {
+                // WM resizeTask must be done after the task is moved to the correct stack,
+                // because Task's setBounds() also updates dim layer's bounds, but that has
+                // dependency on the stack.
+                task.resizeWindowContainer();
             }
         }
 
@@ -2125,39 +2124,246 @@
         if (DEBUG_STACK) Slog.d(TAG_STACK,
                 "findTaskToMoveToFront: moved to front of stack=" + currentStack);
 
-        handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
+        handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY,
                 currentStack.mStackId, forceNonResizeable);
     }
 
-    boolean canUseActivityOptionsLaunchBounds(ActivityOptions options, int launchStackId) {
+    boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
         // We use the launch bounds in the activity options is the device supports freeform
         // window management or is launching into the pinned stack.
-        if (options.getLaunchBounds() == null) {
+        if (options == null || options.getLaunchBounds() == null) {
             return false;
         }
-        return (mService.mSupportsPictureInPicture && launchStackId == PINNED_STACK_ID)
+        return (mService.mSupportsPictureInPicture
+                && options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED)
                 || mService.mSupportsFreeformWindowManagement;
     }
 
     protected <T extends ActivityStack> T getStack(int stackId) {
-        return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final T stack = mActivityDisplays.valueAt(i).getStack(stackId);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
     }
 
-    protected <T extends ActivityStack> T getStack(int stackId, boolean createStaticStackIfNeeded,
-            boolean createOnTop) {
-        final ActivityStack stack = mStacks.get(stackId);
+    /** @see ActivityDisplay#getStack(int, int) */
+    private <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final T stack = mActivityDisplays.valueAt(i).getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+     * @param windowingMode The windowing mode we are checking support for.
+     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+     * @param supportsFreeform If we should consider support for freeform multi-window.
+     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+     * @param activityType The activity type under consideration.
+     * @return true if the windowing mode is supported.
+     */
+    boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+            int activityType) {
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED
+                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            return true;
+        }
+        if (!supportsMultiWindow) {
+            return false;
+        }
+
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+                    windowingMode, activityType);
+        }
+
+        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+            return false;
+        }
+        return true;
+    }
+
+    private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task, int activityType) {
+
+        // First preference if the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        // If windowing mode is unset, then next preference is the candidate task, then the
+        // activity record.
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            if (task != null) {
+                windowingMode = task.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+                windowingMode = r.getWindowingMode();
+            }
+        }
+
+        // Make sure the windowing mode we are trying to use makes sense for what is supported.
+        boolean supportsMultiWindow = mService.mSupportsMultiWindow;
+        boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow;
+        boolean supportsFreeform = mService.mSupportsFreeformWindowManagement;
+        boolean supportsPip = mService.mSupportsPictureInPicture;
+        if (supportsMultiWindow) {
+            if (task != null) {
+                supportsMultiWindow = task.isResizeable();
+                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+                // TODO: Do we need to check for freeform and Pip support here?
+            } else if (r != null) {
+                supportsMultiWindow = r.isResizeable();
+                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+                supportsFreeform = r.supportsFreeform();
+                supportsPip = r.supportsPictureInPicture();
+            }
+        }
+
+        if (isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                supportsFreeform, supportsPip, activityType)) {
+            return windowingMode;
+        }
+        return WINDOWING_MODE_FULLSCREEN;
+    }
+
+    int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task) {
+        // Preference is given to the activity type for the activity then the task since the type
+        // once set shouldn't change.
+        int activityType = r != null ? r.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+        if (activityType == ACTIVITY_TYPE_UNDEFINED && task != null) {
+            activityType = task.getActivityType();
+        }
+        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+            return activityType;
+        }
+        return options != null ? options.getLaunchActivityType() : ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
+        return getLaunchStack(r, options, candidateTask, onTop, INVALID_DISPLAY);
+    }
+
+    /**
+     * Returns the right stack to use for launching factoring in all the input parameters.
+     *
+     * @param r The activity we are trying to launch. Can be null.
+     * @param options The activity options used to the launch. Can be null.
+     * @param candidateTask The possible task the activity might be launched in. Can be null.
+     *
+     * @return The stack to use for the launch or INVALID_STACK_ID.
+     */
+    <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
+            int candidateDisplayId) {
+        int taskId = INVALID_TASK_ID;
+        int displayId = INVALID_DISPLAY;
+        //Rect bounds = null;
+
+        // We give preference to the launch preference in activity options.
+        if (options != null) {
+            taskId = options.getLaunchTaskId();
+            displayId = options.getLaunchDisplayId();
+            // TODO: Need to work this into the equation...
+            //bounds = options.getLaunchBounds();
+        }
+
+        // First preference for stack goes to the task Id set in the activity options. Use the stack
+        // associated with that if possible.
+        if (taskId != INVALID_TASK_ID) {
+            // 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);
+            options.setLaunchTaskId(taskId);
+            if (task != null) {
+                return task.getStack();
+            }
+        }
+
+        final int activityType = resolveActivityType(r, options, candidateTask);
+        int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+        T stack = null;
+
+        // Next preference for stack goes to the display Id set in the activity options or the
+        // candidate display.
+        if (displayId == INVALID_DISPLAY) {
+            displayId = candidateDisplayId;
+        }
+        if (displayId != INVALID_DISPLAY) {
+            if (r != null) {
+                // TODO: This should also take in the windowing mode and activity type into account.
+                stack = (T) getValidLaunchStackOnDisplay(displayId, r);
+                if (stack != null) {
+                    return stack;
+                }
+            }
+            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;
+                    }
+                }
+                // TODO: We should create the stack we want on the display at this point.
+            }
+        }
+
+        // Give preference to the stack and display of the input task and activity if they match the
+        // mode we want to launch into.
+        stack = null;
+        ActivityDisplay display = null;
+        if (candidateTask != null) {
+            stack = candidateTask.getStack();
+        }
+        if (stack == null && r != null) {
+            stack = r.getStack();
+        }
         if (stack != null) {
-            return (T) stack;
+            if (stack.isCompatible(windowingMode, activityType)) {
+                return stack;
+            }
+            display = stack.getDisplay();
         }
-        if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
-            return null;
+
+        if (display == null
+                // TODO: Can be removed once we figure-out how non-standard types should launch
+                // outside the default display.
+                || (activityType != ACTIVITY_TYPE_STANDARD
+                && activityType != ACTIVITY_TYPE_UNDEFINED)) {
+            display = getDefaultDisplay();
         }
-        if (stackId == DOCKED_STACK_ID) {
-            // Make sure recents stack exist when creating a dock stack as it normally need to be on
-            // the other side of the docked stack and we make visibility decisions based on that.
-            getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, createOnTop);
+
+        stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+        if (stack != null) {
+            return stack;
         }
-        return (T) createStackOnDisplay(stackId, DEFAULT_DISPLAY, createOnTop);
+
+        // 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);
     }
 
     /**
@@ -2174,26 +2380,50 @@
                     "Display with displayId=" + displayId + " not found.");
         }
 
+        if (!r.canBeLaunchedOnDisplay(displayId)) {
+            return null;
+        }
+
         // 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);
-            if (mService.mActivityStarter.isValidLaunchStackId(stack.mStackId, displayId, r)) {
+            if (isValidLaunchStack(stack, displayId, r)) {
                 return stack;
             }
         }
 
         // If there is no valid stack on the external display - check if new dynamic stack will do.
-        if (displayId != Display.DEFAULT_DISPLAY) {
-            final int newDynamicStackId = getNextStackId();
-            if (mService.mActivityStarter.isValidLaunchStackId(newDynamicStackId, displayId, r)) {
-                return createStackOnDisplay(newDynamicStackId, displayId, true /*onTop*/);
-            }
+        if (displayId != DEFAULT_DISPLAY) {
+            return activityDisplay.createStack(
+                    r.getWindowingMode(), r.getActivityType(), true /*onTop*/);
         }
 
         Slog.w(TAG, "getValidLaunchStackOnDisplay: can't launch on displayId " + displayId);
         return null;
     }
 
+    // TODO: Can probably be consolidated into getLaunchStack()...
+    private boolean isValidLaunchStack(ActivityStack stack, int displayId, ActivityRecord r) {
+        switch (stack.getActivityType()) {
+            case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
+            case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
+            case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant();
+        }
+        switch (stack.getWindowingMode()) {
+            case WINDOWING_MODE_FULLSCREEN: return true;
+            case WINDOWING_MODE_FREEFORM: return r.supportsFreeform();
+            case WINDOWING_MODE_PINNED: return r.supportsPictureInPicture();
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return r.supportsSplitScreenWindowingMode();
+            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode();
+        }
+
+        if (StackId.isDynamicStack(stack.mStackId)) {
+            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) {
@@ -2225,7 +2455,7 @@
             for (int j = stacks.size() - 1; j >= 0; --j) {
                 final ActivityStack stack = stacks.get(j);
                 if (stack != currentFocus && stack.isFocusable()
-                        && stack.shouldBeVisible(null) != STACK_INVISIBLE) {
+                        && stack.shouldBeVisible(null)) {
                     return stack;
                 }
             }
@@ -2294,7 +2524,7 @@
             return;
         }
 
-        final boolean splitScreenActive = getStack(DOCKED_STACK_ID) != null;
+        final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenStack();
         if (!allowResizeInDockedMode
                 && !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
             // If the docked stack exists, don't resize non-floating stacks independently of the
@@ -2305,7 +2535,7 @@
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
         mWindowManager.deferSurfaceLayout();
         try {
-            if (stack.supportSplitScreenWindowingMode()) {
+            if (stack.supportsSplitScreenWindowingMode()) {
                 if (bounds == null && stack.inSplitScreenWindowingMode()) {
                     // null bounds = fullscreen windowing mode...at least for now.
                     stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -2326,26 +2556,25 @@
         }
     }
 
-    private void deferUpdateBounds(int stackId) {
-        final ActivityStack stack = getStack(stackId);
+    private void deferUpdateBounds(int activityType) {
+        final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
         if (stack != null) {
             stack.deferUpdateBounds();
         }
     }
 
-    private void continueUpdateBounds(int stackId) {
-        final ActivityStack stack = getStack(stackId);
+    private void continueUpdateBounds(int activityType) {
+        final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
         if (stack != null) {
             stack.continueUpdateBounds();
         }
     }
 
     void notifyAppTransitionDone() {
-        continueUpdateBounds(RECENTS_STACK_ID);
+        continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
         for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
             final int taskId = mResizingTasksDuringAnimation.valueAt(i);
-            final TaskRecord task =
-                    anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+            final TaskRecord task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY);
             if (task != null) {
                 task.setTaskDockedResizing(false);
             }
@@ -2353,29 +2582,31 @@
         mResizingTasksDuringAnimation.clear();
     }
 
-    private void moveTasksToFullscreenStackInSurfaceTransaction(int fromStackId,
-            boolean onTop) {
-
-        final ActivityStack stack = getStack(fromStackId);
-        if (stack == null) {
-            return;
-        }
+    /**
+     * 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.
+     */
+    private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
+            int toDisplayId, boolean onTop) {
 
         mWindowManager.deferSurfaceLayout();
         try {
-            if (fromStackId == DOCKED_STACK_ID) {
+            final int windowingMode = fromStack.getWindowingMode();
+            final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
+            final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
+
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 // 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.
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    final ActivityStack otherStack = getStack(i);
-                    if (otherStack == null) {
-                        continue;
-                    }
+                final ArrayList<ActivityStack> displayStacks = toDisplay.mStacks;
+                for (int i = displayStacks.size() - 1; i >= 0; --i) {
+                    final ActivityStack otherStack = displayStacks.get(i);
                     if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
                         continue;
                     }
-                    resizeStackLocked(i, null, null, null, PRESERVE_WINDOWS,
+                    resizeStackLocked(otherStack.mStackId, null, null, null, PRESERVE_WINDOWS,
                             true /* allowResizeInDockedMode */, DEFER_RESUME);
                 }
 
@@ -2384,48 +2615,51 @@
                 // resize when we remove task from it below and it is detached from the
                 // display because it no longer contains any tasks.
                 mAllowDockedStackResize = false;
-            } else if (fromStackId == PINNED_STACK_ID) {
-                if (onTop) {
-                    // Log if we are expanding the PiP to fullscreen
-                    MetricsLogger.action(mService.mContext,
-                            ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
-                }
+            } else if (inPinnedWindowingMode && onTop) {
+                // Log if we are expanding the PiP to fullscreen
+                MetricsLogger.action(mService.mContext,
+                        ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
             }
-            ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
-            final boolean isFullscreenStackVisible = fullscreenStack != null &&
-                    fullscreenStack.shouldBeVisible(null) == STACK_VISIBLE;
+
             // If we are moving from the pinned stack, then the animation takes care of updating
             // the picture-in-picture mode.
-            final boolean schedulePictureInPictureModeChange = (fromStackId == PINNED_STACK_ID);
-            final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-            final int size = tasks.size();
-            if (onTop) {
-                for (int i = 0; i < size; i++) {
-                    final TaskRecord task = tasks.get(i);
-                    final boolean isTopTask = i == (size - 1);
-                    if (fromStackId == PINNED_STACK_ID) {
-                        // Update the return-to to reflect where the pinned stack task was moved
-                        // from so that we retain the stack that was previously visible if the
-                        // pinned stack is recreated. See moveActivityToPinnedStackLocked().
-                        task.setTaskToReturnTo(isFullscreenStackVisible ?
-                                ACTIVITY_TYPE_STANDARD : ACTIVITY_TYPE_HOME);
+            final boolean schedulePictureInPictureModeChange = inPinnedWindowingMode;
+            final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
+
+            if (!tasks.isEmpty()) {
+                final int size = tasks.size();
+                final ActivityStack fullscreenStack = toDisplay.getOrCreateStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop);
+
+                if (onTop) {
+                    final int returnToType =
+                            toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
+                    for (int i = 0; i < size; i++) {
+                        final TaskRecord task = tasks.get(i);
+                        final boolean isTopTask = i == (size - 1);
+                        if (inPinnedWindowingMode) {
+                            // Update the return-to to reflect where the pinned stack task was moved
+                            // from so that we retain the stack that was previously visible if the
+                            // pinned stack is recreated. See moveActivityToPinnedStackLocked().
+                            task.setTaskToReturnTo(returnToType);
+                        }
+                        // Defer resume until all the tasks have been moved to the fullscreen stack
+                        task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+                                isTopTask /* animate */, DEFER_RESUME,
+                                schedulePictureInPictureModeChange,
+                                "moveTasksToFullscreenStack - onTop");
                     }
-                    // Defer resume until all the tasks have been moved to the fullscreen stack
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
-                            REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME,
-                            schedulePictureInPictureModeChange,
-                            "moveTasksToFullscreenStack - onTop");
-                }
-            } else {
-                for (int i = 0; i < size; i++) {
-                    final TaskRecord task = tasks.get(i);
-                    // Position the tasks in the fullscreen stack in order at the bottom of the
-                    // stack. Also defer resume until all the tasks have been moved to the
-                    // fullscreen stack.
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, i /* position */,
-                            REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
-                            schedulePictureInPictureModeChange,
-                            "moveTasksToFullscreenStack - NOT_onTop");
+                } else {
+                    for (int i = 0; i < size; i++) {
+                        final TaskRecord task = tasks.get(i);
+                        // Position the tasks in the fullscreen stack in order at the bottom of the
+                        // stack. Also defer resume until all the tasks have been moved to the
+                        // fullscreen stack.
+                        task.reparent(fullscreenStack, i /* position */,
+                                REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
+                                schedulePictureInPictureModeChange,
+                                "moveTasksToFullscreenStack - NOT_onTop");
+                    }
                 }
             }
 
@@ -2437,9 +2671,13 @@
         }
     }
 
-    void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
-        mWindowManager.inSurfaceTransaction(
-                () -> moveTasksToFullscreenStackInSurfaceTransaction(fromStackId, onTop));
+    void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) {
+        moveTasksToFullscreenStackLocked(fromStack, DEFAULT_DISPLAY, onTop);
+    }
+
+    void moveTasksToFullscreenStackLocked(ActivityStack fromStack, int toDisplayId, boolean onTop) {
+        mWindowManager.inSurfaceTransaction(() ->
+                moveTasksToFullscreenStackInSurfaceTransaction(fromStack, toDisplayId, onTop));
     }
 
     void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
@@ -2450,7 +2688,7 @@
                 false /* deferResume */);
     }
 
-    void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
+    private void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
             boolean preserveWindows, boolean deferResume) {
 
@@ -2459,7 +2697,8 @@
             return;
         }
 
-        final ActivityStack stack = getStack(DOCKED_STACK_ID);
+        final ActivityStack stack = getDefaultDisplay().getStack(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         if (stack == null) {
             Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
             return;
@@ -2479,7 +2718,7 @@
                 // The dock stack either was dismissed or went fullscreen, which is kinda the same.
                 // In this case we make all other static stacks fullscreen and move all
                 // docked stack tasks to the fullscreen stack.
-                moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, ON_TOP);
+                moveTasksToFullscreenStackLocked(stack, ON_TOP);
 
                 // stack shouldn't contain anymore activities, so nothing to resume.
                 r = null;
@@ -2488,13 +2727,14 @@
                 // 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 Rect otherTaskRect = new Rect();
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (i == DOCKED_STACK_ID) {
+                for (int i = stacks.size() - 1; i >= 0; --i) {
+                    final ActivityStack current = stacks.get(i);
+                    if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                         continue;
                     }
-                    final ActivityStack current = getStack(i);
-                    if (current == null || !current.supportSplitScreenWindowingMode()) {
+                    if (!current.supportsSplitScreenWindowingMode()) {
                         continue;
                     }
                     // Need to set windowing mode here before we try to get the dock bounds.
@@ -2504,7 +2744,7 @@
                             tempRect /* outStackBounds */,
                             otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */);
 
-                    resizeStackLocked(i, !tempRect.isEmpty() ? tempRect : null,
+                    resizeStackLocked(current.mStackId, !tempRect.isEmpty() ? tempRect : null,
                             !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
                             tempOtherTaskInsetBounds, preserveWindows,
                             true /* allowResizeInDockedMode */, deferResume);
@@ -2521,7 +2761,9 @@
     }
 
     void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
-        final PinnedActivityStack stack = getStack(PINNED_STACK_ID);
+        // TODO(multi-display): Pinned stack display should be passed in.
+        final PinnedActivityStack stack = getDefaultDisplay().getStack(
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
         if (stack == null) {
             Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
             return;
@@ -2559,32 +2801,14 @@
         }
     }
 
-    ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
-        final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);
-        if (activityDisplay == null) {
-            return null;
-        }
-        return createStack(stackId, activityDisplay, onTop);
-
-    }
-
-    ActivityStack createStack(int stackId, ActivityDisplay display, boolean onTop) {
-        switch (stackId) {
-            case PINNED_STACK_ID:
-                return new PinnedActivityStack(display, stackId, this, mRecentTasks, onTop);
-            default:
-                return new ActivityStack(display, stackId, this, mRecentTasks, onTop);
-        }
-    }
-
-    void removeStackInSurfaceTransaction(int stackId) {
+    private void removeStackInSurfaceTransaction(int stackId) {
         final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             return;
         }
 
         final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-        if (stack.getStackId() == PINNED_STACK_ID) {
+        if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
             /**
              * Workaround: Force-stop all the activities in the pinned stack before we reparent them
              * to the fullscreen stack.  This is to guarantee that when we are removing a stack,
@@ -2602,7 +2826,7 @@
                     true /* processPausingActivites */, null /* configuration */);
 
             // Move all the tasks to the bottom of the fullscreen stack
-            moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+            moveTasksToFullscreenStackLocked(pinnedStack, !ON_TOP);
         } else {
             for (int i = tasks.size() - 1; i >= 0; i--) {
                 removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
@@ -2617,8 +2841,23 @@
      * instead moved back onto the fullscreen stack.
      */
     void removeStackLocked(int stackId) {
-        mWindowManager.inSurfaceTransaction(
-                () -> removeStackInSurfaceTransaction(stackId));
+        mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stackId));
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            mActivityDisplays.valueAt(i).removeStacksInWindowingModes(windowingModes);
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            mActivityDisplays.valueAt(i).removeStacksWithActivityTypes(activityTypes);
+        }
     }
 
     /**
@@ -2640,8 +2879,7 @@
      */
     boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
             boolean pauseImmediately) {
-        final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
-                INVALID_STACK_ID);
+        final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
         if (tr != null) {
             tr.removeTaskActivitiesLocked(pauseImmediately);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
@@ -2654,10 +2892,23 @@
         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) {
-            mRecentTasks.remove(tr);
-            tr.removedFromRecents();
+            removeTaskFromRecents(tr);
         }
         ComponentName component = tr.getBaseIntent().getComponent();
         if (component == null) {
@@ -2740,27 +2991,15 @@
     /**
      * Restores a recent task to a stack
      * @param task The recent task to be restored.
-     * @param stackId The stack to restore the task to (default launch stack will be used
-     *                if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}
-     *                or is not a static stack).
+     * @param aOptions The activity options to use for restoration.
      * @return true if the task has been restored successfully.
      */
-    boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
-        if (!StackId.isStaticStack(stackId)) {
-            // If stack is not static (or stack id is invalid) - use the default one.
-            // This means that tasks that were on external displays will be restored on the
-            // primary display.
-            stackId = task.getLaunchStackId();
-        } else if (stackId == DOCKED_STACK_ID && !task.supportsSplitScreen()) {
-            // Preferred stack is the docked stack, but the task can't go in the docked stack.
-            // Put it in the fullscreen stack.
-            stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-        }
-
+    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions) {
+        final ActivityStack stack = getLaunchStack(null, aOptions, task, !ON_TOP);
         final ActivityStack currentStack = task.getStack();
         if (currentStack != null) {
             // Task has already been restored once. See if we need to do anything more
-            if (currentStack.mStackId == stackId) {
+            if (currentStack == stack) {
                 // Nothing else to do since it is already restored in the right stack.
                 return true;
             }
@@ -2769,19 +3008,9 @@
             currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
         }
 
-        final ActivityStack stack =
-                getStack(stackId, CREATE_IF_NEEDED, !ON_TOP);
-
-        if (stack == null) {
-            // What does this mean??? Not sure how we would get here...
-            if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
-                    "Unable to find/create stack to restore recent task=" + task);
-            return false;
-        }
-
-        stack.addTask(task, false /* toTop */, "restoreRecentTask");
+        stack.addTask(task, !ON_TOP, "restoreRecentTask");
         // TODO: move call for creation here and other place into Stack.addTask()
-        task.createWindowContainer(false /* toTop */, true /* showForAllUsers */);
+        task.createWindowContainer(!ON_TOP, true /* showForAllUsers */);
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
                 "Added restored task=" + task + " to stack=" + stack);
         final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -2803,7 +3032,7 @@
             throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown displayId="
                     + displayId);
         }
-        final ActivityStack stack = mStacks.get(stackId);
+        final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId="
                     + stackId);
@@ -2828,8 +3057,10 @@
      * Returns the reparent target stack, creating the stack if necessary.  This call also enforces
      * the various checks on tasks that are going to be reparented from one stack to another.
      */
-    ActivityStack getReparentTargetStack(TaskRecord task, int stackId, boolean toTop) {
+    ActivityStack getReparentTargetStack(TaskRecord task, ActivityStack stack, boolean toTop) {
         final ActivityStack prevStack = task.getStack();
+        final int stackId = stack.mStackId;
+        final boolean inMultiWindowMode = stack.inMultiWindowMode();
 
         // Check that we aren't reparenting to the same stack that the task is already in
         if (prevStack != null && prevStack.mStackId == stackId) {
@@ -2840,22 +3071,22 @@
 
         // Ensure that we aren't trying to move into a multi-window stack without multi-window
         // support
-        if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+        if (inMultiWindowMode && !mService.mSupportsMultiWindow) {
             throw new IllegalArgumentException("Device doesn't support multi-window, can not"
-                    + " reparent task=" + task + " to stackId=" + stackId);
+                    + " reparent task=" + task + " to stack=" + stack);
         }
 
         // Ensure that we're not moving a task to a dynamic stack if device doesn't support
         // multi-display.
-        // TODO(multi-display): Support non-dynamic stacks on secondary displays.
-        if (StackId.isDynamicStack(stackId) && !mService.mSupportsMultiDisplay) {
+        if (stack.mDisplayId != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
             throw new IllegalArgumentException("Device doesn't support multi-display, can not"
                     + " reparent task=" + task + " to stackId=" + stackId);
         }
 
         // Ensure that we aren't trying to move into a freeform stack without freeform
         // support
-        if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
+        if (stack.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                && !mService.mSupportsFreeformWindowManagement) {
             throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
                     + " task=" + task);
         }
@@ -2864,25 +3095,25 @@
         // used for split-screen mode and will cause things like the docked divider to show up. We
         // instead leave the task in its current stack or move it to the fullscreen stack if it
         // isn't currently in a stack.
-        if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
-            stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
+        if (inMultiWindowMode && !task.isResizeable()) {
             Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
                     + " Moving to stackId=" + stackId + " instead.");
+            // Temporarily disable resizeablility of the task as we don't want it to be resized if,
+            // for example, a docked stack is created which will lead to the stack we are moving
+            // from being resized and and its resizeable tasks being resized.
+            try {
+                task.mTemporarilyUnresizable = true;
+                stack = stack.getDisplay().createStack(
+                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
+            } finally {
+                task.mTemporarilyUnresizable = false;
+            }
         }
-
-        // Temporarily disable resizeablility of the task as we don't want it to be resized if, for
-        // example, a docked stack is created which will lead to the stack we are moving from being
-        // resized and and its resizeable tasks being resized.
-        try {
-            task.mTemporarilyUnresizable = true;
-            return getStack(stackId, CREATE_IF_NEEDED, toTop);
-        } finally {
-            task.mTemporarilyUnresizable = false;
-        }
+        return stack;
     }
 
     boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect destBounds) {
-        final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+        final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             throw new IllegalArgumentException(
                     "moveTopStackActivityToPinnedStackLocked: Unknown stackId=" + stackId);
@@ -2912,12 +3143,17 @@
 
         mWindowManager.deferSurfaceLayout();
 
+        final ActivityDisplay display = r.getStack().getDisplay();
+        PinnedActivityStack stack = display.getPinnedStack();
+
         // This will clear the pinned stack by moving an existing task to the full screen stack,
         // ensuring only one task is present.
-        moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+        if (stack != null) {
+            moveTasksToFullscreenStackLocked(stack, !ON_TOP);
+        }
 
         // Need to make sure the pinned stack exist so we can resize it below...
-        final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+        stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
         try {
             final TaskRecord task = r.getTask();
@@ -2941,8 +3177,8 @@
                     moveHomeStackToFront(reason);
                 }
                 // Defer resume until below, and do not schedule PiP changes until we animate below
-                task.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
-                        DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
+                task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
+                        false /* schedulePictureInPictureModeChange */, reason);
             } else {
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
@@ -2957,7 +3193,7 @@
                 r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
 
                 // Defer resume until below, and do not schedule PiP changes until we animate below
-                newTask.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
                         DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
             }
 
@@ -2983,8 +3219,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName,
-                r.getTask().taskId);
+        mService.mTaskChangeNotificationController.notifyActivityPinned(r);
     }
 
     /** Move activity with its stack to front and make the stack focused. */
@@ -3044,6 +3279,9 @@
                         // tasks should always have lower priority than any affinity-matching tasks
                         // in the fullscreen stacks
                         affinityMatch = mTmpFindTaskResult.r;
+                    } else if (DEBUG_TASKS && mTmpFindTaskResult.matchedByRootAffinity) {
+                        Slog.d(TAG_TASKS, "Skipping match on different display "
+                                + mTmpFindTaskResult.r.getDisplayId() + " " + displayId);
                     }
                 }
             }
@@ -3408,14 +3646,17 @@
     boolean switchUserLocked(int userId, UserState uss) {
         final int focusStackId = mFocusedStack.getStackId();
         // We dismiss the docked stack whenever we switch users.
-        moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, focusStackId == DOCKED_STACK_ID);
+        final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenStack();
+        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);
 
         mUserStackInFront.put(mCurrentUser, focusStackId);
-        final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
+        final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId);
         mCurrentUser = userId;
 
         mStartingUsers.add(uss);
@@ -3448,7 +3689,7 @@
     /** Checks whether the userid is a profile of the current user. */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUser) return true;
-        return mService.mUserController.isCurrentProfileLocked(userId);
+        return mService.mUserController.isCurrentProfile(userId);
     }
 
     /**
@@ -3555,15 +3796,9 @@
         pw.print(prefix);
         pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
         pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
-        pw.print(prefix); pw.println("mStacks=" + mStacks);
-        // TODO: move this to LockTaskController
-        final SparseArray<String[]> packages = mService.mLockTaskPackages;
-        if (packages.size() > 0) {
-            pw.print(prefix); pw.println("mLockTaskPackages (userId:packages)=");
-            for (int i = 0; i < packages.size(); ++i) {
-                pw.print(prefix); pw.print(prefix); pw.print(packages.keyAt(i));
-                pw.print(":"); pw.println(Arrays.toString(packages.valueAt(i)));
-            }
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final ActivityDisplay display = mActivityDisplays.valueAt(i);
+            pw.println(prefix + "displayId=" + display.mDisplayId + " mStacks=" + display.mStacks);
         }
         if (!mWaitingForActivityVisible.isEmpty()) {
             pw.print(prefix); pw.println("mWaitingForActivityVisible=");
@@ -3576,6 +3811,26 @@
         mService.mLockTaskController.dump(pw, prefix);
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
+            ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
+            activityDisplay.writeToProto(proto, DISPLAYS);
+        }
+        mKeyguardController.writeToProto(proto, KEYGUARD_CONTROLLER);
+        if (mFocusedStack != null) {
+            proto.write(FOCUSED_STACK_ID, mFocusedStack.mStackId);
+            ActivityRecord focusedActivity = getResumedActivityLocked();
+            if (focusedActivity != null) {
+                focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+            }
+        } else {
+            proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
+        }
+        proto.end(token);
+    }
+
     /**
      * Dump all connected displays' configurations.
      * @param prefix Prefix to apply to each line of the dump.
@@ -3605,8 +3860,7 @@
                 ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
                 for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                     ActivityStack stack = stacks.get(stackNdx);
-                    if (!dumpVisibleStacksOnly ||
-                            stack.shouldBeVisible(null) == STACK_VISIBLE) {
+                    if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
                         activities.addAll(stack.getDumpActivitiesLocked(name));
                     }
                 }
@@ -3832,15 +4086,22 @@
         return getActivityDisplayOrCreateLocked(displayId) != null;
     }
 
+    // TODO: Look into consolidating with getActivityDisplayOrCreateLocked()
     ActivityDisplay getActivityDisplay(int displayId) {
         return mActivityDisplays.get(displayId);
     }
 
+    // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display.
+    ActivityDisplay getDefaultDisplay() {
+        return mActivityDisplays.get(DEFAULT_DISPLAY);
+    }
+
     /**
      * Get an existing instance of {@link ActivityDisplay} or create new if there is a
      * corresponding record in display manager.
      */
-    private ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
+    // TODO: Look into consolidating with getActivityDisplay()
+    ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
         ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
         if (activityDisplay != null) {
             return activityDisplay;
@@ -3855,17 +4116,18 @@
             return null;
         }
         // The display hasn't been added to ActivityManager yet, create a new record now.
-        activityDisplay = new ActivityDisplay(displayId);
-        if (activityDisplay.mDisplay == null) {
-            Slog.w(TAG, "Display " + displayId + " gone before initialization complete");
-            return null;
-        }
-        mActivityDisplays.put(displayId, activityDisplay);
+        activityDisplay = new ActivityDisplay(this, displayId);
+        attachDisplay(activityDisplay);
         calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
         mWindowManager.onDisplayAdded(displayId);
         return activityDisplay;
     }
 
+    @VisibleForTesting
+    void attachDisplay(ActivityDisplay display) {
+        mActivityDisplays.put(display.mDisplayId, display);
+    }
+
     private void calculateDefaultMinimalSizeOfResizeableTasks(ActivityDisplay display) {
         mDefaultMinSizeOfResizeableTask =
                 mService.mContext.getResources().getDimensionPixelSize(
@@ -3893,7 +4155,7 @@
                         // 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.getStackId(), true /* onTop */);
+                        moveTasksToFullscreenStackLocked(stack, true /* onTop */);
                     }
                 }
 
@@ -3955,7 +4217,7 @@
         if (display.mAllSleepTokens.isEmpty()) {
             return;
         }
-        for (SleepTokenImpl token : display.mAllSleepTokens) {
+        for (SleepToken token : display.mAllSleepTokens) {
             mSleepTokens.remove(token);
         }
         display.mAllSleepTokens.clear();
@@ -3963,7 +4225,7 @@
         mService.updateSleepIfNeededLocked();
     }
 
-    private StackInfo getStackInfoLocked(ActivityStack stack) {
+    private StackInfo getStackInfo(ActivityStack stack) {
         final int displayId = stack.mDisplayId;
         final ActivityDisplay display = mActivityDisplays.get(displayId);
         StackInfo info = new StackInfo();
@@ -3971,11 +4233,10 @@
         info.displayId = displayId;
         info.stackId = stack.mStackId;
         info.userId = stack.mCurrentUser;
-        info.visible = stack.shouldBeVisible(null) == STACK_VISIBLE;
+        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.mStacks.indexOf(stack) : 0;
+        info.configuration.setTo(stack.getConfiguration());
 
         ArrayList<TaskRecord> tasks = stack.getAllTasks();
         final int numTasks = tasks.size();
@@ -4004,40 +4265,44 @@
         return info;
     }
 
-    StackInfo getStackInfoLocked(int stackId) {
+    StackInfo getStackInfo(int stackId) {
         ActivityStack stack = getStack(stackId);
         if (stack != null) {
-            return getStackInfoLocked(stack);
+            return getStackInfo(stack);
         }
         return null;
     }
 
+    StackInfo getStackInfo(int windowingMode, int activityType) {
+        final ActivityStack stack = getStack(windowingMode, activityType);
+        return (stack != null) ? getStackInfo(stack) : null;
+    }
+
     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(getStackInfoLocked(stacks.get(ndx)));
+                list.add(getStackInfo(stacks.get(ndx)));
             }
         }
         return list;
     }
 
-    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
             int preferredDisplayId, int actualStackId) {
-        handleNonResizableTaskIfNeeded(task, preferredStackId, preferredDisplayId, actualStackId,
-                false /* forceNonResizable */);
+        handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId,
+                actualStackId, false /* forceNonResizable */);
     }
 
-    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
             int preferredDisplayId, int actualStackId, boolean forceNonResizable) {
         final boolean isSecondaryDisplayPreferred =
-                (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY)
-                || StackId.isDynamicStack(preferredStackId);
+                (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
         final ActivityStack actualStack = getStack(actualStackId);
         final boolean inSplitScreenMode = actualStack != null
                 && actualStack.inSplitScreenWindowingMode();
-        if (((!inSplitScreenMode && preferredStackId != DOCKED_STACK_ID)
+        if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
                 && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
             return;
         }
@@ -4065,7 +4330,8 @@
         }
 
         final ActivityRecord topActivity = task.getTopActivity();
-        if (launchOnSecondaryDisplayFailed || !task.supportsSplitScreen() || forceNonResizable) {
+        if (launchOnSecondaryDisplayFailed
+                || !task.supportsSplitScreenWindowingMode() || forceNonResizable) {
             if (launchOnSecondaryDisplayFailed) {
                 // Display a warning toast that we tried to put a non-resizeable task on a secondary
                 // display with config different from global config.
@@ -4079,7 +4345,12 @@
 
             // 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.
-            moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID);
+
+            final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenStack();
+            if (dockedStack != null) {
+                moveTasksToFullscreenStackLocked(dockedStack,
+                        actualStackId == dockedStack.getStackId());
+            }
         } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
                 && !topActivity.noDisplay) {
             final String packageName = topActivity.appInfo.packageName;
@@ -4284,122 +4555,6 @@
         }
     }
 
-    // TODO: Move to its own file.
-    /** Exactly one of these classes per Display in the system. Capable of holding zero or more
-     * attached {@link ActivityStack}s */
-    class ActivityDisplay extends ConfigurationContainer {
-        /** Actual Display this object tracks. */
-        int mDisplayId;
-        Display mDisplay;
-
-        /** 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<>();
-
-        /** Array of all UIDs that are present on the display. */
-        private IntArray mDisplayAccessUIDs = new IntArray();
-
-        /** All tokens used to put activities on this stack to sleep (including mOffToken) */
-        final ArrayList<SleepTokenImpl> mAllSleepTokens = new ArrayList<>();
-        /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
-        SleepToken mOffToken;
-
-        private boolean mSleeping;
-
-        @VisibleForTesting
-        ActivityDisplay() {
-            mActivityDisplays.put(mDisplayId, this);
-        }
-
-        // After instantiation, check that mDisplay is not null before using this. The alternative
-        // is for this to throw an exception if mDisplayManager.getDisplay() returns null.
-        ActivityDisplay(int displayId) {
-            final Display display = mDisplayManager.getDisplay(displayId);
-            if (display == null) {
-                return;
-            }
-            init(display);
-        }
-
-        void init(Display display) {
-            mDisplay = display;
-            mDisplayId = display.getDisplayId();
-        }
-
-        void attachStack(ActivityStack stack, int position) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack
-                    + " to displayId=" + mDisplayId + " position=" + position);
-            mStacks.add(position, stack);
-            mService.updateSleepIfNeededLocked();
-        }
-
-        void detachStack(ActivityStack stack) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack
-                    + " from displayId=" + mDisplayId);
-            mStacks.remove(stack);
-            mService.updateSleepIfNeededLocked();
-        }
-
-        @Override
-        public String toString() {
-            return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
-        }
-
-        @Override
-        protected int getChildCount() {
-            return mStacks.size();
-        }
-
-        @Override
-        protected ConfigurationContainer getChildAt(int index) {
-            return mStacks.get(index);
-        }
-
-        @Override
-        protected ConfigurationContainer getParent() {
-            return ActivityStackSupervisor.this;
-        }
-
-        boolean isPrivate() {
-            return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
-        }
-
-        boolean isUidPresent(int uid) {
-            for (ActivityStack stack : mStacks) {
-                if (stack.isUidPresent(uid)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        /** Update and get all UIDs that are present on the display and have access to it. */
-        private IntArray getPresentUIDs() {
-            mDisplayAccessUIDs.clear();
-            for (ActivityStack stack : mStacks) {
-                stack.getPresentUIDs(mDisplayAccessUIDs);
-            }
-            return mDisplayAccessUIDs;
-        }
-
-        boolean shouldDestroyContentOnRemove() {
-            return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
-        }
-
-        boolean shouldSleep() {
-            return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
-                    && (mService.mRunningVoice == null);
-        }
-
-        boolean isSleeping() {
-            return mSleeping;
-        }
-
-        void setIsSleeping(boolean asleep) {
-            mSleeping = asleep;
-        }
-    }
-
     ActivityStack findStackBehind(ActivityStack stack) {
         // TODO(multi-display): We are only looking for stacks on the default display.
         final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
@@ -4432,32 +4587,36 @@
         final String callingPackage;
         final Intent intent;
         final int userId;
+        int activityType = ACTIVITY_TYPE_UNDEFINED;
+        int windowingMode = WINDOWING_MODE_UNDEFINED;
         final ActivityOptions activityOptions = (bOptions != null)
                 ? new ActivityOptions(bOptions) : null;
-        final int launchStackId = (activityOptions != null)
-                ? activityOptions.getLaunchStackId() : INVALID_STACK_ID;
-        if (StackId.isHomeOrRecentsStack(launchStackId)) {
+        if (activityOptions != null) {
+            activityType = activityOptions.getLaunchActivityType();
+            windowingMode = activityOptions.getLaunchWindowingMode();
+        }
+        if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
             throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
         mWindowManager.deferSurfaceLayout();
         try {
-            if (launchStackId == DOCKED_STACK_ID) {
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 mWindowManager.setDockedStackCreateState(
                         activityOptions.getDockCreateMode(), null /* initialBounds */);
 
                 // Defer updating the stack in which recents is until the app transition is done, to
                 // not run into issues where we still need to draw the task in recents but the
                 // docked stack is already created.
-                deferUpdateBounds(RECENTS_STACK_ID);
+                deferUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
             }
 
             task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
-                    launchStackId);
+                    activityOptions);
             if (task == null) {
-                continueUpdateBounds(RECENTS_STACK_ID);
+                continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.executeAppTransition();
                 throw new IllegalArgumentException(
                         "startActivityFromRecentsInner: Task " + taskId + " not found.");
@@ -4465,15 +4624,11 @@
 
             // Since we don't have an actual source record here, we assume that the currently
             // focused activity was the source.
-            final ActivityStack focusedStack = getFocusedStack();
-            final ActivityRecord sourceRecord =
-                    focusedStack != null ? focusedStack.topActivity() : null;
+            final ActivityStack stack = getLaunchStack(null, activityOptions, task, ON_TOP);
 
-            if (launchStackId != INVALID_STACK_ID) {
-                if (task.getStackId() != launchStackId) {
-                    task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
-                            DEFER_RESUME, "startActivityFromRecents");
-                }
+            if (stack != null && task.getStack() != stack) {
+                task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                        "startActivityFromRecents");
             }
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
@@ -4492,15 +4647,12 @@
                 // If we are launching the task in the docked stack, put it into resizing mode so
                 // the window renders full-screen with the background filling the void. Also only
                 // call this at the end to make sure that tasks exists on the window manager side.
-                if (launchStackId == DOCKED_STACK_ID) {
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                     setResizingDuringAnimation(task);
                 }
 
                 mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),
-                        ActivityManager.START_TASK_TO_FRONT,
-                        sourceRecord != null
-                                ? sourceRecord.getTask().getStackId() : INVALID_STACK_ID,
-                        sourceRecord, task.getStack());
+                        ActivityManager.START_TASK_TO_FRONT, task.getStack());
                 return ActivityManager.START_TASK_TO_FRONT;
             }
             callingUid = task.mCallingUid;
@@ -4510,7 +4662,7 @@
             userId = task.userId;
             int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
                     null, null, 0, 0, bOptions, userId, task, "startActivityFromRecents");
-            if (launchStackId == DOCKED_STACK_ID) {
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 setResizingDuringAnimation(task);
             }
             return result;
@@ -4532,8 +4684,7 @@
             for (int j = display.mStacks.size() - 1; j >= 0; j--) {
                 final ActivityStack stack = display.mStacks.get(j);
                 // Get top activity from a visible stack and add it to the list.
-                if (stack.shouldBeVisible(null /* starting */)
-                        == ActivityStack.STACK_VISIBLE) {
+                if (stack.shouldBeVisible(null /* starting */)) {
                     final ActivityRecord top = stack.topActivity();
                     if (top != null) {
                         if (stack == mFocusedStack) {
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 16abcfb..d444c66 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -27,18 +27,19 @@
 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.ASSISTANT_STACK_ID;
 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.HOME_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.RECENTS_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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -76,8 +77,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.ANIMATE;
 import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -167,7 +166,8 @@
     private boolean mDoResume;
     private int mStartFlags;
     private ActivityRecord mSourceRecord;
-    private int mSourceDisplayId;
+    // The display to launch the activity onto, barring any strong reason to do otherwise.
+    private int mPreferredDisplayId;
 
     private TaskRecord mInTask;
     private boolean mAddingToTask;
@@ -186,6 +186,12 @@
     private boolean mAvoidMoveToFront;
     private boolean mPowerHintSent;
 
+    // We must track when we deliver the new intent since multiple code paths invoke
+    // {@link #deliverNewIntent}. This is due to early returns in the code path. This flag is used
+    // inside {@link #deliverNewIntent} to suppress duplicate requests and ensure the intent is
+    // delivered at most once.
+    private boolean mIntentDelivered;
+
     private IVoiceInteractionSession mVoiceSession;
     private IVoiceInteractor mVoiceInteractor;
 
@@ -222,7 +228,7 @@
         mDoResume = false;
         mStartFlags = 0;
         mSourceRecord = null;
-        mSourceDisplayId = INVALID_DISPLAY;
+        mPreferredDisplayId = INVALID_DISPLAY;
 
         mInTask = null;
         mAddingToTask = false;
@@ -243,6 +249,8 @@
         mVoiceInteractor = null;
 
         mUsingVr2dDisplay = false;
+
+        mIntentDelivered = false;
     }
 
     ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
@@ -589,9 +597,7 @@
                 auxiliaryResponse.token, auxiliaryResponse.needsPhaseTwo);
     }
 
-    void postStartActivityProcessing(
-            ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord,
-            ActivityStack targetStack) {
+    void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) {
 
         if (ActivityManager.isStartResultFatalError(result)) {
             return;
@@ -613,7 +619,8 @@
         }
 
         if (startedActivityStackId == DOCKED_STACK_ID) {
-            final ActivityStack homeStack = mSupervisor.getStack(HOME_STACK_ID);
+            final ActivityStack homeStack = mSupervisor.getDefaultDisplay().getStack(
+                            WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
             final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
             if (homeStackVisible) {
                 // We launch an activity while being in home stack, which means either launcher or
@@ -1001,8 +1008,7 @@
             mService.mWindowManager.continueSurfaceLayout();
         }
 
-        postStartActivityProcessing(r, result, mSupervisor.getLastStack().mStackId,  mSourceRecord,
-                mTargetStack);
+        postStartActivityProcessing(r, result, mTargetStack);
 
         return result;
     }
@@ -1024,10 +1030,12 @@
 
         ActivityRecord reusedActivity = getReusableIntentActivity();
 
-        final int preferredLaunchStackId =
-                (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
-        final int preferredLaunchDisplayId =
-                (mOptions != null) ? mOptions.getLaunchDisplayId() : DEFAULT_DISPLAY;
+        int preferredWindowingMode = WINDOWING_MODE_UNDEFINED;
+        int preferredLaunchDisplayId = DEFAULT_DISPLAY;
+        if (mOptions != null) {
+            preferredWindowingMode = mOptions.getLaunchWindowingMode();
+            preferredLaunchDisplayId = mOptions.getLaunchDisplayId();
+        }
 
         if (reusedActivity != null) {
             // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
@@ -1077,9 +1085,7 @@
                         // so make sure the task now has the identity of the new intent.
                         top.getTask().setIntent(mStartActivity);
                     }
-                    ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
-                    top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                            mStartActivity.launchedFromPackage);
+                    deliverNewIntent(top);
                 }
             }
 
@@ -1141,7 +1147,6 @@
                 && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                 || mLaunchSingleTop || mLaunchSingleTask);
         if (dontStart) {
-            ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
             // For paranoia, make sure we have correctly resumed the top activity.
             topStack.mLastPausedActivity = null;
             if (mDoResume) {
@@ -1153,12 +1158,12 @@
                 // anything if that is the case, so this is it!
                 return START_RETURN_INTENT_TO_CALLER;
             }
-            top.deliverNewIntentLocked(
-                    mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+
+            deliverNewIntent(top);
 
             // 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(), preferredLaunchStackId,
+            mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
                     preferredLaunchDisplayId, topStack.mStackId);
 
             return START_DELIVERED_TO_TOP;
@@ -1173,8 +1178,7 @@
         if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                 && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
             newTask = true;
-            result = setTaskFromReuseOrCreateNewTask(
-                    taskToAffiliate, preferredLaunchStackId, topStack);
+            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
         } else if (mSourceRecord != null) {
             result = setTaskFromSourceRecord();
         } else if (mInTask != null) {
@@ -1237,11 +1241,11 @@
                         mOptions);
             }
         } else {
-            mTargetStack.addRecentActivityLocked(mStartActivity);
+            mSupervisor.addRecentActivity(mStartActivity);
         }
         mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
 
-        mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredLaunchStackId,
+        mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode,
                 preferredLaunchDisplayId, mTargetStack.mStackId);
 
         return START_SUCCESS;
@@ -1260,7 +1264,7 @@
         mVoiceSession = voiceSession;
         mVoiceInteractor = voiceInteractor;
 
-        mSourceDisplayId = getSourceDisplayId(mSourceRecord, mStartActivity);
+        mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
 
         mLaunchBounds = getOverrideBounds(r, options, inTask);
 
@@ -1515,7 +1519,7 @@
                         !mLaunchSingleTask);
             } else {
                 // Otherwise find the best task to put the activity in.
-                intentActivity = mSupervisor.findTaskLocked(mStartActivity, mSourceDisplayId);
+                intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
             }
         }
         return intentActivity;
@@ -1523,10 +1527,12 @@
 
     /**
      * Returns the ID of the display to use for a new activity. If the device is in VR mode,
-     * then return the Vr mode's virtual display ID. If not, if the source activity has
-     * a explicit display ID set, use that to launch the activity.
+     * then return the Vr mode's virtual display ID. If not,  if the activity was started with
+     * a launchDisplayId, use that. Otherwise, if the source activity has a explicit display ID
+     * set, use that to launch the activity.
      */
-    private int getSourceDisplayId(ActivityRecord sourceRecord, ActivityRecord startingActivity) {
+    private int getPreferedDisplayId(
+            ActivityRecord sourceRecord, ActivityRecord startingActivity, ActivityOptions options) {
         // Check if the Activity is a VR activity. If so, the activity should be launched in
         // main display.
         if (startingActivity != null && startingActivity.requestedVrComponent != null) {
@@ -1543,6 +1549,13 @@
             return displayId;
         }
 
+        // If the caller requested a display, prefer that display.
+        final int launchDisplayId =
+                (options != null) ? options.getLaunchDisplayId() : INVALID_DISPLAY;
+        if (launchDisplayId != INVALID_DISPLAY) {
+            return launchDisplayId;
+        }
+
         displayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
         // If the activity has a displayId set explicitly, launch it on the same displayId.
         if (displayId != INVALID_DISPLAY) {
@@ -1601,12 +1614,11 @@
                         mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
                                 mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
                         mMovedToFront = true;
-                    } else if (launchStack.mStackId == DOCKED_STACK_ID
-                            || launchStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                    } else if (launchStack.inSplitScreenWindowingMode()) {
                         if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                             // If we want to launch adjacent and mTargetStack is not the computed
                             // launch stack - move task to top of computed stack.
-                            intentTask.reparent(launchStack.mStackId, ON_TOP,
+                            intentTask.reparent(launchStack, ON_TOP,
                                     REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                     "launchToSide");
                         } else {
@@ -1623,17 +1635,17 @@
                         // Target and computed stacks are on different displays and we've
                         // found a matching task - move the existing instance to that display and
                         // move it to front.
-                        intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+                        intentActivity.getTask().reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                 "reparentToDisplay");
                         mMovedToFront = true;
-                    } else if (launchStack.getStackId() == StackId.HOME_STACK_ID
-                        && mTargetStack.getStackId() != StackId.HOME_STACK_ID) {
+                    } else if (launchStack.isActivityTypeHome()
+                            && !mTargetStack.isActivityTypeHome()) {
                         // It is possible for the home activity to be in another stack initially.
                         // For example, the activity may have been initially started with an intent
                         // which placed it in the fullscreen stack. To ensure the proper handling of
                         // the activity based on home stack assumptions, we must move it over.
-                        intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+                        intentActivity.getTask().reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                 "reparentingHome");
                         mMovedToFront = true;
@@ -1654,8 +1666,8 @@
             mTargetStack.moveToFront("intentActivityFound");
         }
 
-        mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(), INVALID_STACK_ID,
-                DEFAULT_DISPLAY, mTargetStack.mStackId);
+        mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
+                WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack.mStackId);
 
         // If the caller has requested that the target task be reset, then do so.
         if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
@@ -1675,8 +1687,7 @@
             // Task will be launched over the home stack, so return home.
             task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
             return;
-        } else if (focusedStack != null && focusedStack != task.getStack() &&
-                focusedStack.isActivityTypeAssistant()) {
+        } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) {
             // Task was launched over the assistant stack, so return there
             task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
             return;
@@ -1739,13 +1750,10 @@
             // desires.
             if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
                     && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
-                ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
-                        intentActivity.getTask());
                 if (intentActivity.frontOfTask) {
                     intentActivity.getTask().setIntent(mStartActivity);
                 }
-                intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                        mStartActivity.launchedFromPackage);
+                deliverNewIntent(intentActivity);
             } else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
                 // In this case we are launching the root activity of the task, but with a
                 // different intent. We should start a new instance on top.
@@ -1779,7 +1787,7 @@
     }
 
     private int setTaskFromReuseOrCreateNewTask(
-            TaskRecord taskToAffiliate, int preferredLaunchStackId, ActivityStack topStack) {
+            TaskRecord taskToAffiliate, ActivityStack topStack) {
         mTargetStack = computeStackFocus(
                 mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions);
 
@@ -1793,15 +1801,8 @@
                     mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
                     mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
             addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
-            if (mLaunchBounds != null) {
-                final int stackId = mTargetStack.mStackId;
-                if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                    mService.resizeStack(
-                            stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-                } else {
-                    mStartActivity.getTask().updateOverrideConfiguration(mLaunchBounds);
-                }
-            }
+            updateBounds(mStartActivity.getTask(), mLaunchBounds);
+
             if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
                     + " in new task " + mStartActivity.getTask());
         } else {
@@ -1821,8 +1822,10 @@
             // If stack id is specified in activity options, usually it means that activity is
             // launched not from currently focused stack (e.g. from SysUI or from shell) - in
             // that case we check the target stack.
+            // TODO: Not sure I understand the value or use of the commented out code and the
+            // comment above. See if this causes any issues and why...
             updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags,
-                    preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : topStack);
+                    /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack);
         }
         if (mDoResume) {
             mTargetStack.moveToFront("reuseOrNewTask");
@@ -1830,6 +1833,17 @@
         return START_SUCCESS;
     }
 
+    private void deliverNewIntent(ActivityRecord activity) {
+        if (mIntentDelivered) {
+            return;
+        }
+
+        ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTask());
+        activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
+                mStartActivity.launchedFromPackage);
+        mIntentDelivered = true;
+    }
+
     private int setTaskFromSourceRecord() {
         if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
@@ -1867,8 +1881,8 @@
         if (mTargetStack == null) {
             mTargetStack = sourceStack;
         } else if (mTargetStack != sourceStack) {
-            sourceTask.reparent(mTargetStack.mStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
-                    !ANIMATE, DEFER_RESUME, "launchToSide");
+            sourceTask.reparent(mTargetStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                    DEFER_RESUME, "launchToSide");
         }
 
         final TaskRecord topTask = mTargetStack.topTask();
@@ -1886,7 +1900,7 @@
             mKeepCurTransition = true;
             if (top != null) {
                 ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 // For paranoia, make sure we have correctly resumed the top activity.
                 mTargetStack.mLastPausedActivity = null;
                 if (mDoResume) {
@@ -1905,7 +1919,7 @@
                 task.moveActivityToFrontLocked(top);
                 top.updateOptionsLocked(mOptions);
                 ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task);
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 mTargetStack.mLastPausedActivity = null;
                 if (mDoResume) {
                     mSupervisor.resumeFocusedStackTopActivityLocked();
@@ -1941,14 +1955,12 @@
                     || mLaunchSingleTop || mLaunchSingleTask) {
                 mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
                         mStartActivity.appTimeTracker, "inTaskToFront");
-                ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
                 if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                     // We don't need to start a new activity, and the client said not to do
                     // anything if that is the case, so this is it!
                     return START_RETURN_INTENT_TO_CALLER;
                 }
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                        mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 return START_DELIVERED_TO_TOP;
             }
         }
@@ -1963,17 +1975,15 @@
         }
 
         if (mLaunchBounds != null) {
-            mInTask.updateOverrideConfiguration(mLaunchBounds);
-            int stackId = mInTask.getLaunchStackId();
-            if (stackId != mInTask.getStackId()) {
-                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+            // TODO: Shouldn't we already know what stack to use by the time we get here?
+            ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP);
+            if (stack != mInTask.getStack()) {
+                mInTask.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
                         DEFER_RESUME, "inTaskToFront");
-                stackId = mInTask.getStackId();
                 mTargetStack = mInTask.getStack();
             }
-            if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-            }
+
+            updateBounds(mInTask, mLaunchBounds);
         }
 
         mTargetStack.moveTaskToFrontLocked(
@@ -1986,6 +1996,19 @@
         return START_SUCCESS;
     }
 
+    void updateBounds(TaskRecord task, Rect bounds) {
+        if (bounds == null) {
+            return;
+        }
+
+        final int stackId = task.getStackId();
+        if (StackId.resizeStackWithLaunchBounds(stackId)) {
+            mService.resizeStack(stackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+        } else {
+            task.updateOverrideConfiguration(bounds);
+        }
+    }
+
     private void setTaskToCurrentTopOrCreateNewTask() {
         mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags,
                 mOptions);
@@ -2079,20 +2102,21 @@
             return mSupervisor.mFocusedStack;
         }
 
-        if (mSourceDisplayId != DEFAULT_DISPLAY) {
+        if (mPreferredDisplayId != DEFAULT_DISPLAY) {
             // Try to put the activity in a stack on a secondary display.
-            stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
+            stack = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r);
             if (stack == null) {
                 // If source display is not suitable - look for topmost valid stack in the system.
                 if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                        "computeStackFocus: Can't launch on mSourceDisplayId=" + mSourceDisplayId
-                                + ", looking on all displays.");
-                stack = mSupervisor.getNextValidLaunchStackLocked(r, mSourceDisplayId);
+                        "computeStackFocus: Can't launch on mPreferredDisplayId="
+                                + mPreferredDisplayId + ", looking on all displays.");
+                stack = mSupervisor.getNextValidLaunchStackLocked(r, mPreferredDisplayId);
             }
         }
         if (stack == null) {
             // We first try to put the task in the first dynamic stack on home display.
-            final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
+            final ArrayList<ActivityStack> homeDisplayStacks =
+                    mSupervisor.getStacksOnDefaultDisplay();
             for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 stack = homeDisplayStacks.get(stackNdx);
                 if (isDynamicStack(stack.mStackId)) {
@@ -2102,10 +2126,7 @@
                 }
             }
             // If there is no suitable dynamic stack then we figure out which static stack to use.
-            final int stackId = task != null ? task.getLaunchStackId() :
-                    bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
-                            FULLSCREEN_WORKSPACE_STACK_ID;
-            stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+            stack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
         }
         if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
                 + r + " stackId=" + stack.mStackId);
@@ -2113,37 +2134,40 @@
     }
 
     /** Check if provided activity record can launch in currently focused stack. */
+    // TODO: This method can probably be consolidated into getLaunchStack() below.
     private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
         final ActivityStack focusedStack = mSupervisor.mFocusedStack;
-        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
         final boolean canUseFocusedStack;
-        switch (focusedStackId) {
-            case FULLSCREEN_WORKSPACE_STACK_ID:
-                // The fullscreen stack can contain any task regardless of if the task is resizeable
-                // or not. So, we let the task go in the fullscreen task if it is the focus stack.
-                canUseFocusedStack = true;
-                break;
-            case ASSISTANT_STACK_ID:
-                canUseFocusedStack = r.isActivityTypeAssistant();
-                break;
-            case DOCKED_STACK_ID:
-                // Any activity which supports split screen can go in the docked stack.
-                canUseFocusedStack = r.supportsSplitScreen();
-                break;
-            case FREEFORM_WORKSPACE_STACK_ID:
-                // Any activity which supports freeform can go in the freeform stack.
-                canUseFocusedStack = r.supportsFreeform();
-                break;
-            default:
-                // Dynamic stacks behave similarly to the fullscreen stack and can contain any
-                // resizeable task.
-                canUseFocusedStack = isDynamicStack(focusedStackId)
-                        && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
+        if (focusedStack.isActivityTypeAssistant()) {
+            canUseFocusedStack = r.isActivityTypeAssistant();
+        } else {
+            switch (focusedStack.getWindowingMode()) {
+                case WINDOWING_MODE_FULLSCREEN:
+                    // The fullscreen stack can contain any task regardless of if the task is
+                    // resizeable or not. So, we let the task go in the fullscreen task if it is the
+                    // focus stack.
+                    canUseFocusedStack = true;
+                    break;
+                case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+                case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+                    // Any activity which supports split screen can go in the docked stack.
+                    canUseFocusedStack = r.supportsSplitScreenWindowingMode();
+                    break;
+                case WINDOWING_MODE_FREEFORM:
+                    // Any activity which supports freeform can go in the freeform stack.
+                    canUseFocusedStack = r.supportsFreeform();
+                    break;
+                default:
+                    // Dynamic stacks behave similarly to the fullscreen stack and can contain any
+                    // resizeable task.
+                    canUseFocusedStack = isDynamicStack(focusedStackId)
+                            && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+            }
         }
-
         return canUseFocusedStack && !newTask
-                // We strongly prefer to launch activities on the same display as their source.
-                && (mSourceDisplayId == focusedStack.mDisplayId);
+                // Using the focus stack isn't important enough to override the preferred display.
+                && (mPreferredDisplayId == focusedStack.mDisplayId);
     }
 
     private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
@@ -2153,54 +2177,16 @@
             return mReuseTask.getStack();
         }
 
-        // If the activity is of a specific type, return the associated stack, creating it if
-        // necessary
-        if (r.isActivityTypeHome()) {
-            return mSupervisor.mHomeStack;
-        }
-        if (r.isActivityTypeRecents()) {
-            return mSupervisor.getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (r.isActivityTypeAssistant()) {
-            return mSupervisor.getStack(ASSISTANT_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
+        final int vrDisplayId = mUsingVr2dDisplay ? mPreferredDisplayId : INVALID_DISPLAY;
+        final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
+                vrDisplayId);
 
-        final int launchDisplayId =
-                (aOptions != null) ? aOptions.getLaunchDisplayId() : INVALID_DISPLAY;
-
-        final int launchStackId =
-                (aOptions != null) ? aOptions.getLaunchStackId() : INVALID_STACK_ID;
-
-        if (launchStackId != INVALID_STACK_ID && launchDisplayId != INVALID_DISPLAY) {
-            throw new IllegalArgumentException(
-                    "Stack and display id can't be set at the same time.");
-        }
-
-        if (isValidLaunchStackId(launchStackId, launchDisplayId, r)) {
-            return mSupervisor.getStack(launchStackId, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (launchStackId == DOCKED_STACK_ID) {
-            // The preferred launch stack is the docked stack, but it isn't a valid launch stack
-            // for this activity, so we put the activity in the fullscreen stack.
-            return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (launchDisplayId != INVALID_DISPLAY) {
-            // Stack id has higher priority than display id.
-            return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r);
-        }
-
-        // If we are using Vr2d display, find the virtual display stack.
-        if (mUsingVr2dDisplay) {
-            ActivityStack as = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
-            if (DEBUG_STACK) {
-                Slog.v(TAG, "Launch stack for app: " + r.toString() +
-                    ", on virtual display stack:" + as.toString());
-            }
-            return as;
+        if (launchStack != null) {
+            return launchStack;
         }
 
         if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
-                 || mSourceDisplayId != DEFAULT_DISPLAY) {
+                 || mPreferredDisplayId != DEFAULT_DISPLAY) {
             return null;
         }
         // Otherwise handle adjacent launch.
@@ -2222,15 +2208,16 @@
             if (parentStack != null && parentStack.isDockedStack()) {
                 // If parent was in docked stack, the natural place to launch another activity
                 // will be fullscreen, so it can appear alongside the docked window.
-                return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED,
-                        ON_TOP);
+                final int activityType = mSupervisor.resolveActivityType(r, mOptions, task);
+                return parentStack.getDisplay().getOrCreateStack(
+                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, activityType, ON_TOP);
             } else {
                 // 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.getStack(DOCKED_STACK_ID);
-                if (dockedStack != null
-                        && dockedStack.shouldBeVisible(r) == STACK_INVISIBLE) {
+                final ActivityStack dockedStack = mSupervisor.getDefaultDisplay().getStack(
+                                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+                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;
                 } else {
@@ -2240,39 +2227,11 @@
         }
     }
 
-    boolean isValidLaunchStackId(int stackId, int displayId, ActivityRecord r) {
-        switch (stackId) {
-            case INVALID_STACK_ID:
-            case HOME_STACK_ID:
-                return false;
-            case FULLSCREEN_WORKSPACE_STACK_ID:
-                return true;
-            case FREEFORM_WORKSPACE_STACK_ID:
-                return r.supportsFreeform();
-            case DOCKED_STACK_ID:
-                return r.supportsSplitScreen();
-            case PINNED_STACK_ID:
-                return r.supportsPictureInPicture();
-            case RECENTS_STACK_ID:
-                return r.isActivityTypeRecents();
-            case ASSISTANT_STACK_ID:
-                return r.isActivityTypeAssistant();
-            default:
-                if (StackId.isDynamicStack(stackId)) {
-                    return r.canBeLaunchedOnDisplay(displayId);
-                }
-                Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
-                return false;
-        }
-    }
-
-    Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
+    private Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
         Rect newBounds = null;
-        if (options != null && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
-            if (mSupervisor.canUseActivityOptionsLaunchBounds(
-                    options, options.getLaunchStackId())) {
-                newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
-            }
+        if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
+                && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
+            newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
         }
         return newBounds;
     }
@@ -2281,15 +2240,6 @@
         mWindowManager = wm;
     }
 
-    void removePendingActivityLaunchesLocked(ActivityStack stack) {
-        for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
-            PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
-            if (pal.stack == stack) {
-                mPendingActivityLaunches.remove(palNdx);
-            }
-        }
-    }
-
     static boolean isDocumentLaunchesIntoExisting(int flags) {
         return (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
                 (flags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
diff --git a/com/android/server/am/AppErrors.java b/com/android/server/am/AppErrors.java
index 440b3d3..fe38097 100644
--- a/com/android/server/am/AppErrors.java
+++ b/com/android/server/am/AppErrors.java
@@ -507,7 +507,7 @@
         // launching the report UI under a different user.
         app.errorReportReceiver = null;
 
-        for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+        for (int userId : mService.mUserController.getCurrentProfileIds()) {
             if (app.userId == userId) {
                 app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                         mContext, app.info.packageName, app.info.flags);
@@ -728,7 +728,7 @@
             boolean isBackground = (UserHandle.getAppId(proc.uid)
                     >= Process.FIRST_APPLICATION_UID
                     && proc.pid != MY_PID);
-            for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+            for (int userId : mService.mUserController.getCurrentProfileIds()) {
                 isBackground &= (proc.userId != userId);
             }
             if (isBackground && !showBackground) {
diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java
index 3105e37..7c9cd00 100644
--- a/com/android/server/am/BatteryStatsService.java
+++ b/com/android/server/am/BatteryStatsService.java
@@ -220,7 +220,7 @@
         // Shutdown the thread we made.
         mWorker.shutdown();
     }
-    
+
     public static IBatteryStats getService() {
         if (sService != null) {
             return sService;
@@ -300,10 +300,10 @@
 
             // TODO: remove this once we figure out properly where and how
             // PROCESS_EVENT = 1112
-            // EVENT SUBTYPE: START = 1
-            // KEY_NAME: 1
+            // KEY_STATE = 1
+            // KEY_PACKAGE_NAME: 1002
             // KEY_UID: 2
-            StatsLog.writeArray(1112, 1, 1, name, 2, uid);
+            StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid);
         }
     }
 
@@ -313,10 +313,10 @@
 
             // TODO: remove this once we figure out properly where and how
             // PROCESS_EVENT = 1112
-            // EVENT SUBTYPE: CRASH = 2
-            // KEY_NAME: 1
+            // KEY_STATE = 1
+            // KEY_PACKAGE_NAME: 1002
             // KEY_UID: 2
-            StatsLog.writeArray(1112, 2, 1, name, 2, uid);
+            StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid);
         }
     }
 
@@ -550,10 +550,10 @@
         synchronized (mStats) {
             mStats.noteScreenStateLocked(state);
             // TODO: remove this once we figure out properly where and how
-            // SCREEN_EVENT = 1003
-            // State key: 1
+            // SCREEN_EVENT = 2
+            // KEY_STATE: 1
             // State value: state. We can change this to our own def later.
-            StatsLog.writeArray(1003, 1, state);
+            StatsLog.writeArray(2, 1, state);
         }
         if (DBG) Slog.d(TAG, "end noteScreenState");
     }
@@ -564,14 +564,14 @@
             mStats.noteScreenBrightnessLocked(brightness);
         }
     }
-    
+
     public void noteUserActivity(int uid, int event) {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.noteUserActivityLocked(uid, event);
         }
     }
-    
+
     public void noteWakeUp(String reason, int reasonUid) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -611,21 +611,21 @@
             mStats.notePhoneOnLocked();
         }
     }
-    
+
     public void notePhoneOff() {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.notePhoneOffLocked();
         }
     }
-    
+
     public void notePhoneSignalStrength(SignalStrength signalStrength) {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.notePhoneSignalStrengthLocked(signalStrength);
         }
     }
-    
+
     public void notePhoneDataConnectionState(int dataType, boolean hasData) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -647,7 +647,7 @@
             mStats.noteWifiOnLocked();
         }
     }
-    
+
     public void noteWifiOff() {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -806,7 +806,7 @@
             mStats.noteFullWifiLockAcquiredLocked(uid);
         }
     }
-    
+
     public void noteFullWifiLockReleased(int uid) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -1043,7 +1043,7 @@
             });
         });
     }
-    
+
     public long getAwakeTimeBattery() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
@@ -1186,6 +1186,7 @@
 
         int flags = 0;
         boolean useCheckinFormat = false;
+        boolean toProto = false;
         boolean isRealCheckin = false;
         boolean noOutput = false;
         boolean writeData = false;
@@ -1212,6 +1213,8 @@
                 } else if ("-c".equals(arg)) {
                     useCheckinFormat = true;
                     flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
+                } else if ("--proto".equals(arg)) {
+                    toProto = true;
                 } else if ("--charged".equals(arg)) {
                     flags |= BatteryStats.DUMP_CHARGED_ONLY;
                 } else if ("--daily".equals(arg)) {
@@ -1304,7 +1307,45 @@
             }
         }
 
-        if (useCheckinFormat) {
+        if (toProto) {
+            List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+                    PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
+            if (isRealCheckin) {
+                // For a real checkin, first we want to prefer to use the last complete checkin
+                // file if there is one.
+                synchronized (mStats.mCheckinFile) {
+                    if (mStats.mCheckinFile.exists()) {
+                        try {
+                            byte[] raw = mStats.mCheckinFile.readFully();
+                            if (raw != null) {
+                                Parcel in = Parcel.obtain();
+                                in.unmarshall(raw, 0, raw.length);
+                                in.setDataPosition(0);
+                                BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+                                        null, mStats.mHandler, null, mUserManagerUserInfoProvider);
+                                checkinStats.readSummaryFromParcel(in);
+                                in.recycle();
+                                checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
+                                        historyStart);
+                                mStats.mCheckinFile.delete();
+                                return;
+                            }
+                        } catch (IOException | ParcelFormatException e) {
+                            Slog.w(TAG, "Failure reading checkin file "
+                                    + mStats.mCheckinFile.getBaseFile(), e);
+                        }
+                    }
+                }
+            }
+            if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid());
+            synchronized (mStats) {
+                mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart);
+                if (writeData) {
+                    mStats.writeAsyncLocked();
+                }
+            }
+            if (DBG) Slog.d(TAG, "end dumpProtoLocked");
+        } else if (useCheckinFormat) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
                     PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
             if (isRealCheckin) {
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index cea80c8..5c48f90 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -19,12 +19,15 @@
 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;
 import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
 import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
 import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 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.PRESERVE_WINDOWS;
+import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_OCCLUDED;
+import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_SHOWING;
 import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
 import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
 import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
@@ -38,6 +41,7 @@
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.wm.WindowManagerService;
@@ -66,6 +70,7 @@
     private int mBeforeUnoccludeTransit;
     private int mVisibilityTransactionDepth;
     private SleepToken mSleepToken;
+    private int mSecondaryDisplayShowing = INVALID_DISPLAY;
 
     KeyguardController(ActivityManagerService service,
             ActivityStackSupervisor stackSupervisor) {
@@ -78,10 +83,12 @@
     }
 
     /**
-     * @return true if Keyguard is showing, not going away, and not being occluded, false otherwise
+     * @return true if Keyguard is showing, not going away, and not being occluded on the given
+     *         display, false otherwise
      */
-    boolean isKeyguardShowing() {
-        return mKeyguardShowing && !mKeyguardGoingAway && !mOccluded;
+    boolean isKeyguardShowing(int displayId) {
+        return mKeyguardShowing && !mKeyguardGoingAway &&
+                (displayId == DEFAULT_DISPLAY ? !mOccluded : displayId == mSecondaryDisplayShowing);
     }
 
     /**
@@ -94,15 +101,19 @@
     /**
      * Update the Keyguard showing state.
      */
-    void setKeyguardShown(boolean showing) {
-        if (showing == mKeyguardShowing) {
+    void setKeyguardShown(boolean showing, int secondaryDisplayShowing) {
+        boolean showingChanged = showing != mKeyguardShowing;
+        if (!showingChanged && secondaryDisplayShowing == mSecondaryDisplayShowing) {
             return;
         }
         mKeyguardShowing = showing;
-        dismissDockedStackIfNeeded();
-        if (showing) {
-            setKeyguardGoingAway(false);
-            mDismissalRequested = false;
+        mSecondaryDisplayShowing = secondaryDisplayShowing;
+        if (showingChanged) {
+            dismissDockedStackIfNeeded();
+            if (showing) {
+                setKeyguardGoingAway(false);
+                mDismissalRequested = false;
+            }
         }
         mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         updateKeyguardSleepToken();
@@ -331,15 +342,19 @@
             // 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.
-            mStackSupervisor.moveTasksToFullscreenStackLocked(DOCKED_STACK_ID,
-                    mStackSupervisor.mFocusedStack.getStackId() == DOCKED_STACK_ID);
+            final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+            if (stack == null) {
+                return;
+            }
+            mStackSupervisor.moveTasksToFullscreenStackLocked(stack,
+                    mStackSupervisor.mFocusedStack == stack);
         }
     }
 
     private void updateKeyguardSleepToken() {
-        if (mSleepToken == null && isKeyguardShowing()) {
+        if (mSleepToken == null && isKeyguardShowing(DEFAULT_DISPLAY)) {
             mSleepToken = mService.acquireSleepToken("Keyguard", DEFAULT_DISPLAY);
-        } else if (mSleepToken != null && !isKeyguardShowing()) {
+        } else if (mSleepToken != null && !isKeyguardShowing(DEFAULT_DISPLAY)) {
             mSleepToken.release();
             mSleepToken = null;
         }
@@ -354,4 +369,11 @@
         pw.println(prefix + "  mDismissalRequested=" + mDismissalRequested);
         pw.println(prefix + "  mVisibilityTransactionDepth=" + mVisibilityTransactionDepth);
     }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(KEYGUARD_SHOWING, mKeyguardShowing);
+        proto.write(KEYGUARD_OCCLUDED, mOccluded);
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index d8706bc..72b5de8 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -25,12 +25,14 @@
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.app.StatusBarManager.DISABLE_RECENT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.DEVICE_POLICY_SERVICE;
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
 import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED;
 import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -55,7 +57,9 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 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;
@@ -65,6 +69,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Helper class that deals with all things related to task locking. This includes the screen pinning
@@ -123,6 +128,11 @@
     private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
 
     /**
+     * Packages that are allowed to be launched into the lock task mode for each user.
+     */
+    private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
+
+    /**
      * Store the current lock task mode. Possible values:
      * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
      * {@link ActivityManager#LOCK_TASK_MODE_PINNED}
@@ -159,8 +169,8 @@
     }
 
     /**
-     * @return whether the given task can be moved to the back of the stack. Locked tasks cannot be
-     * moved to the back of the stack.
+     * @return whether the given task is locked at the moment. Locked tasks cannot be moved to the
+     * back of the stack.
      */
     boolean checkLockedTask(TaskRecord task) {
         if (mLockTaskModeTasks.contains(task)) {
@@ -422,8 +432,8 @@
             mSupervisor.resumeFocusedStackTopActivityLocked();
             mWindowManager.executeAppTransition();
         } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
-            mSupervisor.handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
-                    task.getStackId(), true /* forceNonResizable */);
+            mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
+                    DEFAULT_DISPLAY, task.getStackId(), true /* forceNonResizable */);
         }
     }
 
@@ -452,29 +462,35 @@
     }
 
     /**
-     * Called when the list of packages whitelisted for lock task mode is changed. Any currently
-     * locked tasks that got removed from the whitelist will be finished.
+     * Update packages that are allowed to be launched in lock task mode.
+     * @param userId Which user this whitelist is associated with
+     * @param packages The whitelist of packages allowed in lock task mode
+     * @see #mLockTaskPackages
      */
-    // TODO: Missing unit tests
-    void onLockTaskPackagesUpdated() {
-        boolean didSomething = false;
+    void updateLockTaskPackages(int userId, String[] packages) {
+        mLockTaskPackages.put(userId, packages);
+
+        boolean taskChanged = false;
         for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx);
-            final boolean wasWhitelisted =
-                    (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
-                            (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
+            final boolean wasWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
             lockedTask.setLockTaskAuth();
-            final boolean isWhitelisted =
-                    (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
-                            (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
-            if (wasWhitelisted && !isWhitelisted) {
-                // Lost whitelisting authorization. End it now.
-                if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
-                        lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
-                removeLockedTask(lockedTask);
-                lockedTask.performClearTaskLocked();
-                didSomething = true;
+            final boolean isWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
+
+            if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED
+                    || lockedTask.userId != userId
+                    || !wasWhitelisted || isWhitelisted) {
+                continue;
             }
+
+            // Terminate locked tasks that have recently lost whitelist authorization.
+            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
+                    lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
+            removeLockedTask(lockedTask);
+            lockedTask.performClearTaskLocked();
+            taskChanged = true;
         }
 
         for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) {
@@ -484,22 +500,40 @@
                 stack.onLockTaskPackagesUpdatedLocked();
             }
         }
+
         final ActivityRecord r = mSupervisor.topRunningActivityLocked();
-        final TaskRecord task = r != null ? r.getTask() : null;
-        if (mLockTaskModeTasks.isEmpty() && task != null
+        final TaskRecord task = (r != null) ? r.getTask() : null;
+        if (mLockTaskModeTasks.isEmpty() && task!= null
                 && task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) {
             // This task must have just been authorized.
             if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK,
                     "onLockTaskPackagesUpdated: starting new locktask task=" + task);
-            setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated",
-                    false);
-            didSomething = true;
+            setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated", false);
+            taskChanged = true;
         }
-        if (didSomething) {
+
+        if (taskChanged) {
             mSupervisor.resumeFocusedStackTopActivityLocked();
         }
     }
 
+    boolean isPackageWhitelisted(int userId, String pkg) {
+        if (pkg == null) {
+            return false;
+        }
+        String[] whitelist;
+        whitelist = mLockTaskPackages.get(userId);
+        if (whitelist == null) {
+            return false;
+        }
+        for (String whitelistedPkg : whitelist) {
+            if (pkg.equals(whitelistedPkg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * @return the topmost locked task
      */
@@ -556,8 +590,18 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mLockTaskModeState=" + lockTaskModeToString());
-        pw.println(" mLockTaskModeTasks" + mLockTaskModeTasks);
+        pw.println(prefix + "LockTaskController");
+        prefix = prefix + "  ";
+        pw.println(prefix + "mLockTaskModeState=" + lockTaskModeToString());
+        pw.println(prefix + "mLockTaskModeTasks=");
+        for (int i = 0; i < mLockTaskModeTasks.size(); ++i) {
+            pw.println(prefix + "  #" + i + " " + mLockTaskModeTasks.get(i));
+        }
+        pw.println(prefix + "mLockTaskPackages (userId:packages)=");
+        for (int i = 0; i < mLockTaskPackages.size(); ++i) {
+            pw.println(prefix + "  u" + mLockTaskPackages.keyAt(i)
+                    + ":" + Arrays.toString(mLockTaskPackages.valueAt(i)));
+        }
     }
 
     private String lockTaskModeToString() {
diff --git a/com/android/server/am/PendingIntentRecord.java b/com/android/server/am/PendingIntentRecord.java
index ee59386..7930f53 100644
--- a/com/android/server/am/PendingIntentRecord.java
+++ b/com/android/server/am/PendingIntentRecord.java
@@ -308,7 +308,7 @@
                 boolean sendFinish = finishedReceiver != null;
                 int userId = key.userId;
                 if (userId == UserHandle.USER_CURRENT) {
-                    userId = owner.mUserController.getCurrentOrTargetUserIdLocked();
+                    userId = owner.mUserController.getCurrentOrTargetUserId();
                 }
                 int res = 0;
                 switch (key.type) {
diff --git a/com/android/server/am/PinnedActivityStack.java b/com/android/server/am/PinnedActivityStack.java
index a601ee1..2726979 100644
--- a/com/android/server/am/PinnedActivityStack.java
+++ b/com/android/server/am/PinnedActivityStack.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import android.app.RemoteAction;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -32,15 +35,16 @@
 class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
         implements PinnedStackWindowListener {
 
-    PinnedActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
-            ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
-        super(display, stackId, supervisor, recentTasks, onTop);
+    PinnedActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+            boolean onTop) {
+        super(display, stackId, supervisor, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, onTop);
     }
 
     @Override
     PinnedStackWindowController createStackWindowController(int displayId, boolean onTop,
             Rect outBounds) {
-        return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds);
+        return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds,
+                mStackSupervisor.mWindowManager);
     }
 
     Rect getDefaultPictureInPictureBounds(float aspectRatio) {
diff --git a/com/android/server/am/ProcessStatsService.java b/com/android/server/am/ProcessStatsService.java
index 39aed7c..effb86c 100644
--- a/com/android/server/am/ProcessStatsService.java
+++ b/com/android/server/am/ProcessStatsService.java
@@ -23,11 +23,14 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.service.procstats.ProcessStatsProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.DumpUtils;
@@ -622,13 +625,17 @@
 
         long ident = Binder.clearCallingIdentity();
         try {
-            dumpInner(fd, pw, args);
+            if (args.length > 0 && "--proto".equals(args[0])) {
+                dumpProto(fd);
+            } else {
+                dumpInner(pw, args);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void dumpInner(PrintWriter pw, String[] args) {
         final long now = SystemClock.uptimeMillis();
 
         boolean isCheckin = false;
@@ -1038,4 +1045,44 @@
             }
         }
     }
+
+    private void dumpAggregatedStats(ProtoOutputStream proto, int aggregateHours, long now) {
+        ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000
+                - (ProcessStats.COMMIT_PERIOD/2));
+        if (pfd == null) {
+            return;
+        }
+        ProcessStats stats = new ProcessStats(false);
+        InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        stats.read(stream);
+        if (stats.mReadError != null) {
+            return;
+        }
+        stats.toProto(proto, now);
+    }
+
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        // dump current procstats
+        long nowToken = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_NOW);
+        long now;
+        synchronized (mAm) {
+            now = SystemClock.uptimeMillis();
+            mProcessStats.toProto(proto, now);
+        }
+        proto.end(nowToken);
+
+        // aggregated over last 3 hours procstats
+        long tokenOf3Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_3HRS);
+        dumpAggregatedStats(proto, 3, now);
+        proto.end(tokenOf3Hrs);
+
+        // aggregated over last 24 hours procstats
+        long tokenOf24Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_24HRS);
+        dumpAggregatedStats(proto, 24, now);
+        proto.end(tokenOf24Hrs);
+
+        proto.flush();
+    }
 }
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index 027dc08..ac85e6b 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -517,11 +517,14 @@
                             } catch (PackageManager.NameNotFoundException e) {
                             }
                         }
-                        if (localForegroundNoti.getSmallIcon() == null) {
+                        if (localForegroundNoti.getSmallIcon() == null
+                                || nm.getNotificationChannel(localPackageName, appUid,
+                                localForegroundNoti.getChannelId()) == 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/TaskChangeNotificationController.java b/com/android/server/am/TaskChangeNotificationController.java
index 8297169..5a7e7ce 100644
--- a/com/android/server/am/TaskChangeNotificationController.java
+++ b/com/android/server/am/TaskChangeNotificationController.java
@@ -95,7 +95,8 @@
     };
 
     private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
-        l.onActivityPinned((String) m.obj, m.arg1);
+        final ActivityRecord r = (ActivityRecord) m.obj;
+        l.onActivityPinned(r.packageName, r.userId, r.getTask().taskId, r.getStackId());
     };
 
     private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
@@ -278,10 +279,9 @@
     }
 
     /** Notifies all listeners when an Activity is pinned. */
-    void notifyActivityPinned(String packageName, int taskId) {
+    void notifyActivityPinned(ActivityRecord r) {
         mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
-        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
-                taskId, 0, packageName);
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, r);
         forAllLocalListeners(mNotifyActivityPinned, msg);
         msg.sendToTarget();
     }
diff --git a/com/android/server/am/TaskPersister.java b/com/android/server/am/TaskPersister.java
index 74c4826..61994b5 100644
--- a/com/android/server/am/TaskPersister.java
+++ b/com/android/server/am/TaskPersister.java
@@ -54,9 +54,6 @@
 import java.util.Comparator;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
 
 public class TaskPersister {
@@ -472,8 +469,7 @@
 
                                 final int taskId = task.taskId;
                                 if (mStackSupervisor.anyTaskForIdLocked(taskId,
-                                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
-                                        INVALID_STACK_ID) != null) {
+                                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
                                     // Should not happen.
                                     Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
                                 } else if (userId != task.userId) {
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 0e65184..0d8df79 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -18,19 +18,19 @@
 
 import static android.app.ActivityManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-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.HOME_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.RECENTS_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;
 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.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;
@@ -65,6 +65,20 @@
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_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;
+import static com.android.server.am.proto.TaskRecordProto.BOUNDS;
+import static com.android.server.am.proto.TaskRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.TaskRecordProto.FULLSCREEN;
+import static com.android.server.am.proto.TaskRecordProto.ID;
+import static com.android.server.am.proto.TaskRecordProto.LAST_NON_FULLSCREEN_BOUNDS;
+import static com.android.server.am.proto.TaskRecordProto.MIN_HEIGHT;
+import static com.android.server.am.proto.TaskRecordProto.MIN_WIDTH;
+import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY;
+import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY;
+import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE;
+import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE;
+import static com.android.server.am.proto.TaskRecordProto.STACK_ID;
+import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE;
 
 import static java.lang.Integer.MAX_VALUE;
 
@@ -78,7 +92,6 @@
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityManager;
-import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -95,6 +108,7 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
@@ -508,8 +522,7 @@
             updateOverrideConfiguration(bounds);
             if (getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
                 // re-restore the task so it can have the proper stack association.
-                mService.mStackSupervisor.restoreRecentTaskLocked(this,
-                        FREEFORM_WORKSPACE_STACK_ID);
+                mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
             }
             return true;
         }
@@ -559,36 +572,36 @@
     /**
      * Convenience method to reparent a task to the top or bottom position of the stack.
      */
-    boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, String reason) {
-        return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
-                deferResume, true /* schedulePictureInPictureModeChange */, reason);
+    boolean reparent(ActivityStack preferredStack, boolean toTop,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            String reason) {
+        return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate, deferResume,
+                true /* schedulePictureInPictureModeChange */, reason);
     }
 
     /**
      * Convenience method to reparent a task to the top or bottom position of the stack, with
      * an option to skip scheduling the picture-in-picture mode change.
      */
-    boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
-            String reason) {
-        return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
+    boolean reparent(ActivityStack preferredStack, boolean toTop,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            boolean schedulePictureInPictureModeChange, String reason) {
+        return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate,
                 deferResume, schedulePictureInPictureModeChange, reason);
     }
 
-    /**
-     * Convenience method to reparent a task to a specific position of the stack.
-     */
-    boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, String reason) {
-        return reparent(preferredStackId, position, moveStackMode, animate, deferResume,
+    /** Convenience method to reparent a task to a specific position of the stack. */
+    boolean reparent(ActivityStack preferredStack, int position,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            String reason) {
+        return reparent(preferredStack, position, moveStackMode, animate, deferResume,
                 true /* schedulePictureInPictureModeChange */, reason);
     }
 
     /**
      * Reparents the task into a preferred stack, creating it if necessary.
      *
-     * @param preferredStackId the stack id of the target stack to move this task
+     * @param preferredStack the target stack to move this task
      * @param position the position to place this task in the new stack
      * @param animate whether or not we should wait for the new window created as a part of the
      *            reparenting to be drawn and animated in
@@ -602,13 +615,16 @@
      * @param reason the caller of this reparenting
      * @return whether the task was reparented
      */
-    boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
-            String reason) {
+    // 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} */
+    boolean reparent(ActivityStack preferredStack, int position,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            boolean schedulePictureInPictureModeChange, String reason) {
         final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
         final WindowManagerService windowManager = mService.mWindowManager;
         final ActivityStack sourceStack = getStack();
-        final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStackId,
+        final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack,
                 position == MAX_VALUE);
         if (toStack == sourceStack) {
             return false;
@@ -691,19 +707,22 @@
             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
                     && !Objects.equals(mBounds, toStack.mBounds)) {
                 kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
                         deferResume);
-            } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
+            } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
                     toStack.layoutTaskInStack(this, null);
                     bounds = mBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
-            } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
-                if (stackId == DOCKED_STACK_ID && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
+            } else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) {
+                if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
                     // Move recents to front so it is not behind home stack when going into docked
                     // mode
                     mService.mStackSupervisor.moveRecentsStackToFront(reason);
@@ -730,10 +749,12 @@
         }
 
         // TODO: Handle incorrect request to move before the actual move, not after.
-        supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, DEFAULT_DISPLAY, stackId);
+        final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenStack();
+        supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
+                DEFAULT_DISPLAY, stackId);
 
-        boolean successful = (preferredStackId == stackId);
-        if (successful && stackId == DOCKED_STACK_ID) {
+        boolean successful = (preferredStack == toStack);
+        if (successful && toStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // If task moved to docked stack - show recents if needed.
             mService.mWindowManager.showRecentApps(false /* fromHome */);
         }
@@ -857,8 +878,13 @@
         }
         mResizeMode = info.resizeMode;
         mSupportsPictureInPicture = info.supportsPictureInPicture();
-        mLockTaskMode = info.lockTaskLaunchMode;
         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();
     }
 
@@ -921,8 +947,8 @@
         mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
     }
 
-    ActivityStack getStack() {
-        return mStack;
+    <T extends ActivityStack> T getStack() {
+        return (T) mStack;
     }
 
     /**
@@ -1396,16 +1422,11 @@
     }
 
     void setLockTaskAuth() {
-        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;
-        }
+        final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
         switch (mLockTaskMode) {
             case LOCK_TASK_LAUNCH_MODE_DEFAULT:
-                mLockTaskAuth = isLockTaskWhitelistedLocked() ?
-                    LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
+                mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+                        ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
                 break;
 
             case LOCK_TASK_LAUNCH_MODE_NEVER:
@@ -1417,31 +1438,14 @@
                 break;
 
             case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
-                mLockTaskAuth = isLockTaskWhitelistedLocked() ?
-                        LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
+                mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+                        ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
                 break;
         }
         if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "setLockTaskAuth: task=" + this +
                 " mLockTaskAuth=" + lockTaskAuthToString());
     }
 
-    private boolean isLockTaskWhitelistedLocked() {
-        String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
-        if (pkg == null) {
-            return false;
-        }
-        String[] packages = mService.mLockTaskPackages.get(userId);
-        if (packages == null) {
-            return false;
-        }
-        for (int i = packages.length - 1; i >= 0; --i) {
-            if (pkg.equals(packages[i])) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     boolean isOverHomeStack() {
         return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
     }
@@ -1459,10 +1463,12 @@
         return isResizeable(true /* checkSupportsPip */);
     }
 
-    boolean supportsSplitScreen() {
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
         // A task can not be docked even if it is considered resizeable because it only supports
         // picture-in-picture mode but has a non-resizeable resizeMode
-        return mService.mSupportsSplitScreenMultiWindow
+        return super.supportsSplitScreenWindowingMode()
+                && mService.mSupportsSplitScreenMultiWindow
                 && (mService.mForceResizableActivities
                         || (isResizeable(false /* checkSupportsPip */)
                                 && !ActivityInfo.isPreserveOrientationMode(mResizeMode)));
@@ -2097,39 +2103,16 @@
         }
     }
 
-    /**
-     * Returns the correct stack to use based on task type and currently set bounds,
-     * regardless of the focused stack and current stack association of the task.
-     * The task will be moved (and stack focus changed) later if necessary.
-     */
-    int getLaunchStackId() {
-        if (isActivityTypeRecents()) {
-            return RECENTS_STACK_ID;
-        }
-        if (isActivityTypeHome()) {
-            return HOME_STACK_ID;
-        }
-        if (isActivityTypeAssistant()) {
-            return ASSISTANT_STACK_ID;
-        }
-        if (mBounds != null) {
-            return FREEFORM_WORKSPACE_STACK_ID;
-        }
-        return FULLSCREEN_WORKSPACE_STACK_ID;
-    }
-
     /** Returns the bounds that should be used to launch this task. */
     private Rect getLaunchBounds() {
         if (mStack == null) {
             return null;
         }
 
-        final int stackId = mStack.mStackId;
-        if (stackId == HOME_STACK_ID
-                || stackId == RECENTS_STACK_ID
-                || stackId == ASSISTANT_STACK_ID
-                || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                || (stackId == DOCKED_STACK_ID && !isResizeable())) {
+        final int windowingMode = getWindowingMode();
+        if (!isActivityTypeStandardOrUndefined()
+                || windowingMode == WINDOWING_MODE_FULLSCREEN
+                || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
             return isResizeable() ? mStack.mBounds : null;
         } else if (!getWindowConfiguration().persistTaskBounds()) {
             return mStack.mBounds;
@@ -2275,4 +2258,34 @@
         stringName = sb.toString();
         return toString();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        proto.write(ID, taskId);
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            ActivityRecord activity = mActivities.get(i);
+            activity.writeToProto(proto, ACTIVITIES);
+        }
+        proto.write(STACK_ID, mStack.mStackId);
+        if (mLastNonFullscreenBounds != null) {
+            mLastNonFullscreenBounds.writeToProto(proto, LAST_NON_FULLSCREEN_BOUNDS);
+        }
+        if (realActivity != null) {
+            proto.write(REAL_ACTIVITY, realActivity.flattenToShortString());
+        }
+        if (origActivity != null) {
+            proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
+        }
+        proto.write(ACTIVITY_TYPE, getActivityType());
+        proto.write(RETURN_TO_TYPE, mTaskToReturnTo);
+        proto.write(RESIZE_MODE, mResizeMode);
+        proto.write(FULLSCREEN, mFullscreen);
+        if (mBounds != null) {
+            mBounds.writeToProto(proto, BOUNDS);
+        }
+        proto.write(MIN_WIDTH, mMinWidth);
+        proto.write(MIN_HEIGHT, mMinHeight);
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index db6bb7d..5a29594 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -22,6 +22,7 @@
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -30,14 +31,6 @@
 import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL;
 import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE;
 import static com.android.server.am.ActivityManagerService.MY_PID;
-import static com.android.server.am.ActivityManagerService.REPORT_LOCKED_BOOT_COMPLETE_MSG;
-import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
-import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_UNLOCK_MSG;
-import static com.android.server.am.ActivityManagerService.USER_SWITCH_CALLBACKS_TIMEOUT_MSG;
-import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
 import static com.android.server.am.UserState.STATE_BOOTING;
 import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
@@ -68,6 +61,7 @@
 import android.os.IProgressListener;
 import android.os.IRemoteCallback;
 import android.os.IUserManager;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -78,6 +72,7 @@
 import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
@@ -93,6 +88,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
+import com.android.server.SystemServiceManager;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -107,25 +103,64 @@
 
 /**
  * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
+ *
+ * <p>This class use {@link #mLock} to synchronize access to internal state. Methods that require
+ * {@link #mLock} to be held should have "LU" suffix in the name.
+ *
+ * <p><strong>Important:</strong> Synchronized code, i.e. one executed inside a synchronized(mLock)
+ * block or inside LU method, should only access internal state of this class or make calls to
+ * other LU methods. Non-LU method calls or calls to external classes are discouraged as they
+ * may cause lock inversion.
  */
-class UserController {
+class UserController implements Handler.Callback {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
 
-    // Maximum number of users we allow to be running at a time.
+    /**
+     * Maximum number of users we allow to be running at a time, including the system user and
+     * its profiles.
+     * Note changing this to 2 is not recommended, since that would mean, if the user uses
+     * work profile and then switch to a secondary user, then the work profile user would be killed,
+     * which should work fine, but aggressively killing the work profile user that has just been
+     * running could cause data loss.  (Even without work profile, witching from secondary user A
+     * to secondary user B would cause similar issues on user B.)
+     *
+     * TODO: Consider adding or replacing with "MAX_RUNNING_*SECONDARY*_USERS", which is the max
+     * number of running *secondary, switchable* users.
+     */
     static final int MAX_RUNNING_USERS = 3;
 
     // Amount of time we wait for observers to handle a user switch before
     // giving up on them and unfreezing the screen.
     static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
 
+    // ActivityManager thread message constants
+    static final int REPORT_USER_SWITCH_MSG = 10;
+    static final int CONTINUE_USER_SWITCH_MSG = 20;
+    static final int USER_SWITCH_TIMEOUT_MSG = 30;
+    static final int START_PROFILES_MSG = 40;
+    static final int SYSTEM_USER_START_MSG = 50;
+    static final int SYSTEM_USER_CURRENT_MSG = 60;
+    static final int FOREGROUND_PROFILE_CHANGED_MSG = 70;
+    static final int REPORT_USER_SWITCH_COMPLETE_MSG = 80;
+    static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 90;
+    static final int SYSTEM_USER_UNLOCK_MSG = 100;
+    static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
+    static final int START_USER_SWITCH_FG_MSG = 120;
+
+    // UI thread message constants
+    static final int START_USER_SWITCH_UI_MSG = 1000;
+
     // If a callback wasn't called within USER_SWITCH_CALLBACKS_TIMEOUT_MS after
     // USER_SWITCH_TIMEOUT_MS, an error is reported. Usually it indicates a problem in the observer
     // when it never calls back.
     private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
 
-    private final Object mLock;
+    // Lock for internal state.
+    private final Object mLock = new Object();
+
     private final Injector mInjector;
     private final Handler mHandler;
+    private final Handler mUiHandler;
 
     // Holds the current foreground user's id. Use mLock when updating
     @GuardedBy("mLock")
@@ -161,7 +196,8 @@
     /**
      * Mapping from each known user ID to the profile group ID it is associated with.
      */
-    private final SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+    @GuardedBy("mLock")
+    private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();
 
     /**
      * Registered observers of the user switching mechanics.
@@ -192,26 +228,25 @@
     @VisibleForTesting
     UserController(Injector injector) {
         mInjector = injector;
-        mLock = injector.getLock();
-        mHandler = injector.getHandler();
+        mHandler = mInjector.getHandler(this);
+        mUiHandler = mInjector.getUiHandler(this);
         // User 0 is the first and only user that runs at boot.
         final UserState uss = new UserState(UserHandle.SYSTEM);
         mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
         mUserLru.add(UserHandle.USER_SYSTEM);
         mLockPatternUtils = mInjector.getLockPatternUtils();
-        updateStartedUserArrayLocked();
+        updateStartedUserArrayLU();
     }
 
     void finishUserSwitch(UserState uss) {
+        finishUserBoot(uss);
+        startProfiles();
         synchronized (mLock) {
-            finishUserBoot(uss);
-
-            startProfilesLocked();
-            stopRunningUsersLocked(MAX_RUNNING_USERS);
+            stopRunningUsersLU(MAX_RUNNING_USERS);
         }
     }
 
-    void stopRunningUsersLocked(int maxRunningUsers) {
+    void stopRunningUsersLU(int maxRunningUsers) {
         int num = mUserLru.size();
         int i = 0;
         while (num > maxRunningUsers && i < mUserLru.size()) {
@@ -240,7 +275,7 @@
                 continue;
             }
             // This is a user to be stopped.
-            if (stopUsersLocked(oldUserId, false, null) != USER_OP_SUCCESS) {
+            if (stopUsersLU(oldUserId, false, null) != USER_OP_SUCCESS) {
                 num--;
             }
             num--;
@@ -258,55 +293,57 @@
         Slog.d(TAG, "Finishing user boot " + userId);
         synchronized (mLock) {
             // Bail if we ended up with a stale user
-            if (mStartedUsers.get(userId) != uss) return;
+            if (mStartedUsers.get(userId) != uss) {
+                return;
+            }
+        }
 
-            // We always walk through all the user lifecycle states to send
-            // consistent developer events. We step into RUNNING_LOCKED here,
-            // but we might immediately step into RUNNING below if the user
-            // storage is already unlocked.
-            if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                // Do not report secondary users, runtime restarts or first boot/upgrade
-                if (userId == UserHandle.USER_SYSTEM
-                        && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-                    int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
-                    MetricsLogger.histogram(mInjector.getContext(),
-                            "framework_locked_boot_completed", uptimeSeconds);
-                    final int MAX_UPTIME_SECONDS = 120;
-                    if (uptimeSeconds > MAX_UPTIME_SECONDS) {
-                        Slog.wtf("SystemServerTiming",
-                                "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
-                    }
+        // We always walk through all the user lifecycle states to send
+        // consistent developer events. We step into RUNNING_LOCKED here,
+        // but we might immediately step into RUNNING below if the user
+        // storage is already unlocked.
+        if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
+            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+            // Do not report secondary users, runtime restarts or first boot/upgrade
+            if (userId == UserHandle.USER_SYSTEM
+                    && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+                int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
+                MetricsLogger.histogram(mInjector.getContext(),
+                        "framework_locked_boot_completed", uptimeSeconds);
+                final int MAX_UPTIME_SECONDS = 120;
+                if (uptimeSeconds > MAX_UPTIME_SECONDS) {
+                    Slog.wtf("SystemServerTiming",
+                            "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
                 }
-
-                mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
-                        userId, 0));
-                Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                mInjector.broadcastIntentLocked(intent, null, resultTo, 0, null, null,
-                        new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
-                        AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
             }
 
-            // We need to delay unlocking managed profiles until the parent user
-            // is also unlocked.
-            if (mInjector.getUserManager().isManagedProfile(userId)) {
-                final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
-                if (parent != null
-                        && isUserRunningLocked(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
-                    Slog.d(TAG, "User " + userId + " (parent " + parent.id
-                            + "): attempting unlock because parent is unlocked");
-                    maybeUnlockUser(userId);
-                } else {
-                    String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
-                    Slog.d(TAG, "User " + userId + " (parent " + parentId
-                            + "): delaying unlock because parent is locked");
-                }
-            } else {
+            mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
+                    userId, 0));
+            Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
+                    new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                    AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
+        }
+
+        // We need to delay unlocking managed profiles until the parent user
+        // is also unlocked.
+        if (mInjector.getUserManager().isManagedProfile(userId)) {
+            final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+            if (parent != null
+                    && isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
+                Slog.d(TAG, "User " + userId + " (parent " + parent.id
+                        + "): attempting unlock because parent is unlocked");
                 maybeUnlockUser(userId);
+            } else {
+                String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
+                Slog.d(TAG, "User " + userId + " (parent " + parentId
+                        + "): delaying unlock because parent is locked");
             }
+        } else {
+            maybeUnlockUser(userId);
         }
     }
 
@@ -316,34 +353,30 @@
      */
     private void finishUserUnlocking(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
-        boolean proceedWithUnlock = false;
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
 
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            if (uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                proceedWithUnlock = true;
+            // Do not proceed if unexpected state
+            if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+                return;
             }
         }
+        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+        uss.mUnlockProgress.start();
 
-        if (proceedWithUnlock) {
-            uss.mUnlockProgress.start();
+        // Prepare app storage before we go any further
+        uss.mUnlockProgress.setProgress(5,
+                    mInjector.getContext().getString(R.string.android_start_title));
+        mInjector.getUserManager().onBeforeUnlockUser(userId);
+        uss.mUnlockProgress.setProgress(20);
 
-            // Prepare app storage before we go any further
-            uss.mUnlockProgress.setProgress(5,
-                        mInjector.getContext().getString(R.string.android_start_title));
-            mInjector.getUserManager().onBeforeUnlockUser(userId);
-            uss.mUnlockProgress.setProgress(20);
-
-            // Dispatch unlocked to system services; when fully dispatched,
-            // that calls through to the next "unlocked" phase
-            mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
-                    .sendToTarget();
-        }
+        // Dispatch unlocked to system services; when fully dispatched,
+        // that calls through to the next "unlocked" phase
+        mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+                .sendToTarget();
     }
 
     /**
@@ -352,64 +385,64 @@
      */
     void finishUserUnlocked(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
 
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            if (uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                uss.mUnlockProgress.finish();
-
-                // Dispatch unlocked to external apps
-                final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
-                unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                unlockedIntent.addFlags(
-                        Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-                mInjector.broadcastIntentLocked(unlockedIntent, null, null, 0, null,
-                        null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
-                        userId);
-
-                if (getUserInfo(userId).isManagedProfile()) {
-                    UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
-                    if (parent != null) {
-                        final Intent profileUnlockedIntent = new Intent(
-                                Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-                        profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
-                        profileUnlockedIntent.addFlags(
-                                Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                                | Intent.FLAG_RECEIVER_FOREGROUND);
-                        mInjector.broadcastIntentLocked(profileUnlockedIntent,
-                                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, false, false, MY_PID, SYSTEM_UID,
-                                parent.id);
-                    }
-                }
-
-                // Send PRE_BOOT broadcasts if user fingerprint changed; we
-                // purposefully block sending BOOT_COMPLETED until after all
-                // PRE_BOOT receivers are finished to avoid ANR'ing apps
-                final UserInfo info = getUserInfo(userId);
-                if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
-                    // Suppress double notifications for managed profiles that
-                    // were unlocked automatically as part of their parent user
-                    // being unlocked.
-                    final boolean quiet;
-                    if (info.isManagedProfile()) {
-                        quiet = !uss.tokenProvided
-                                || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
-                    } else {
-                        quiet = false;
-                    }
-                    mInjector.sendPreBootBroadcast(userId, quiet,
-                            () -> finishUserUnlockedCompleted(uss));
-                } else {
-                    finishUserUnlockedCompleted(uss);
-                }
+            // Do not proceed if unexpected state
+            if (!uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
+                return;
             }
         }
+        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+        uss.mUnlockProgress.finish();
+        // Dispatch unlocked to external apps
+        final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+        unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        unlockedIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
+                null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                userId);
+
+        if (getUserInfo(userId).isManagedProfile()) {
+            UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+            if (parent != null) {
+                final Intent profileUnlockedIntent = new Intent(
+                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
+                profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+                profileUnlockedIntent.addFlags(
+                        Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                                | Intent.FLAG_RECEIVER_FOREGROUND);
+                mInjector.broadcastIntent(profileUnlockedIntent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, false, false, MY_PID, SYSTEM_UID,
+                        parent.id);
+            }
+        }
+
+        // Send PRE_BOOT broadcasts if user fingerprint changed; we
+        // purposefully block sending BOOT_COMPLETED until after all
+        // PRE_BOOT receivers are finished to avoid ANR'ing apps
+        final UserInfo info = getUserInfo(userId);
+        if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
+            // Suppress double notifications for managed profiles that
+            // were unlocked automatically as part of their parent user
+            // being unlocked.
+            final boolean quiet;
+            if (info.isManagedProfile()) {
+                quiet = !uss.tokenProvided
+                        || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+            } else {
+                quiet = false;
+            }
+            mInjector.sendPreBootBroadcast(userId, quiet,
+                    () -> finishUserUnlockedCompleted(uss));
+        } else {
+            finishUserUnlockedCompleted(uss);
+        }
     }
 
     private void finishUserUnlockedCompleted(UserState uss) {
@@ -417,60 +450,59 @@
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
-            final UserInfo userInfo = getUserInfo(userId);
-            if (userInfo == null) {
-                return;
-            }
-
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            // Remember that we logged in
-            mInjector.getUserManager().onUserLoggedIn(userId);
-
-            if (!userInfo.isInitialized()) {
-                if (userId != UserHandle.USER_SYSTEM) {
-                    Slog.d(TAG, "Initializing user #" + userId);
-                    Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
-                    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                            | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                    mInjector.broadcastIntentLocked(intent, null,
-                            new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered,
-                                        boolean sticky, int sendingUser) {
-                                    // Note: performReceive is called with mService lock held
-                                    mInjector.getUserManager().makeInitialized(userInfo.id);
-                                }
-                            }, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, SYSTEM_UID, userId);
-                }
-            }
-
-            Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
-            // Do not report secondary users, runtime restarts or first boot/upgrade
-            if (userId == UserHandle.USER_SYSTEM
-                    && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-                int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
-                MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
-                        uptimeSeconds);
-            }
-            final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-            bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-            bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            mInjector.broadcastIntentLocked(bootIntent, null, new IIntentReceiver.Stub() {
-                @Override
-                public void performReceive(Intent intent, int resultCode, String data,
-                        Bundle extras, boolean ordered, boolean sticky, int sendingUser)
-                        throws RemoteException {
-                    Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
-                }
-            }, 0, null, null,
-                    new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
-                    AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
         }
+        UserInfo userInfo = getUserInfo(userId);
+        if (userInfo == null) {
+            return;
+        }
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
+
+        // Remember that we logged in
+        mInjector.getUserManager().onUserLoggedIn(userId);
+
+        if (!userInfo.isInitialized()) {
+            if (userId != UserHandle.USER_SYSTEM) {
+                Slog.d(TAG, "Initializing user #" + userId);
+                Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                mInjector.broadcastIntent(intent, null,
+                        new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky, int sendingUser) {
+                                // Note: performReceive is called with mService lock held
+                                mInjector.getUserManager().makeInitialized(userInfo.id);
+                            }
+                        }, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, true, false, MY_PID, SYSTEM_UID, userId);
+            }
+        }
+
+        Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
+        // Do not report secondary users, runtime restarts or first boot/upgrade
+        if (userId == UserHandle.USER_SYSTEM
+                && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+            int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
+            MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
+                    uptimeSeconds);
+        }
+        final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+        bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+                            throws RemoteException {
+                        Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+                    }
+                }, 0, null, null,
+                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
     }
 
     int restartUser(final int userId, final boolean foreground) {
@@ -499,35 +531,35 @@
         if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
             throw new IllegalArgumentException("Can't stop system user " + userId);
         }
-        mInjector.enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
+        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
-            return stopUsersLocked(userId, force, callback);
+            return stopUsersLU(userId, force, callback);
         }
     }
 
     /**
      * Stops the user along with its related users. The method calls
-     * {@link #getUsersToStopLocked(int)} to determine the list of users that should be stopped.
+     * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
      */
-    private int stopUsersLocked(final int userId, boolean force, final IStopUserCallback callback) {
+    private int stopUsersLU(final int userId, boolean force, final IStopUserCallback callback) {
         if (userId == UserHandle.USER_SYSTEM) {
             return USER_OP_ERROR_IS_SYSTEM;
         }
-        if (isCurrentUserLocked(userId)) {
+        if (isCurrentUserLU(userId)) {
             return USER_OP_IS_CURRENT;
         }
-        int[] usersToStop = getUsersToStopLocked(userId);
+        int[] usersToStop = getUsersToStopLU(userId);
         // If one of related users is system or current, no related users should be stopped
         for (int i = 0; i < usersToStop.length; i++) {
             int relatedUserId = usersToStop[i];
-            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLocked(relatedUserId)) {
+            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
                 if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked cannot stop related user "
                         + relatedUserId);
                 // We still need to stop the requested user if it's a force stop.
                 if (force) {
                     Slog.i(TAG,
                             "Force stop user " + userId + ". Related users will not be stopped");
-                    stopSingleUserLocked(userId, callback);
+                    stopSingleUserLU(userId, callback);
                     return USER_OP_SUCCESS;
                 }
                 return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -535,25 +567,22 @@
         }
         if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
         for (int userIdToStop : usersToStop) {
-            stopSingleUserLocked(userIdToStop, userIdToStop == userId ? callback : null);
+            stopSingleUserLU(userIdToStop, userIdToStop == userId ? callback : null);
         }
         return USER_OP_SUCCESS;
     }
 
-    private void stopSingleUserLocked(final int userId, final IStopUserCallback callback) {
+    private void stopSingleUserLU(final int userId, final IStopUserCallback callback) {
         if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
         final UserState uss = mStartedUsers.get(userId);
         if (uss == null) {
             // User is not started, nothing to do...  but we do need to
             // callback if requested.
             if (callback != null) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            callback.userStopped(userId);
-                        } catch (RemoteException e) {
-                        }
+                mHandler.post(() -> {
+                    try {
+                        callback.userStopped(userId);
+                    } catch (RemoteException e) {
                     }
                 });
             }
@@ -568,10 +597,10 @@
                 && uss.state != UserState.STATE_SHUTDOWN) {
             uss.setState(UserState.STATE_STOPPING);
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-            updateStartedUserArrayLocked();
+            updateStartedUserArrayLU();
 
-            long ident = Binder.clearCallingIdentity();
-            try {
+            // Post to handler to obtain amLock
+            mHandler.post(() -> {
                 // We are going to broadcast ACTION_USER_STOPPING and then
                 // once that is done send a final ACTION_SHUTDOWN and then
                 // stop the user.
@@ -584,31 +613,24 @@
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                finishUserStopping(userId, uss);
-                            }
-                        });
+                        mHandler.post(() -> finishUserStopping(userId, uss));
                     }
                 };
+
                 // Clear broadcast queue for the user to avoid delivering stale broadcasts
-                mInjector.clearBroadcastQueueForUserLocked(userId);
+                mInjector.clearBroadcastQueueForUser(userId);
                 // Kick things off.
-                mInjector.broadcastIntentLocked(stoppingIntent,
+                mInjector.broadcastIntent(stoppingIntent,
                         null, stoppingReceiver, 0, null, null,
                         new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                         null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+            });
         }
     }
 
     void finishUserStopping(final int userId, final UserState uss) {
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
-        shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         // This is the result receiver for the final shutdown broadcast.
         final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
             @Override
@@ -635,20 +657,19 @@
         mInjector.batteryStatsServiceNoteEvent(
                 BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
                 Integer.toString(userId), userId);
-        mInjector.systemServiceManagerStopUser(userId);
+        mInjector.getSystemServiceManager().stopUser(userId);
 
-        synchronized (mLock) {
-            mInjector.broadcastIntentLocked(shutdownIntent,
-                    null, shutdownReceiver, 0, null, null, null,
-                    AppOpsManager.OP_NONE,
-                    null, true, false, MY_PID, SYSTEM_UID, userId);
-        }
+        mInjector.broadcastIntent(shutdownIntent,
+                null, shutdownReceiver, 0, null, null, null,
+                AppOpsManager.OP_NONE,
+                null, true, false, MY_PID, SYSTEM_UID, userId);
     }
 
     void finishUserStopped(UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         boolean stopped;
         ArrayList<IStopUserCallback> callbacks;
+        boolean forceStopUser = false;
         synchronized (mLock) {
             callbacks = new ArrayList<>(uss.mStopCallbacks);
             if (mStartedUsers.get(userId) != uss) {
@@ -659,16 +680,18 @@
                 stopped = true;
                 // User can no longer run.
                 mStartedUsers.remove(userId);
-                mInjector.getUserManagerInternal().removeUserState(userId);
                 mUserLru.remove(Integer.valueOf(userId));
-                updateStartedUserArrayLocked();
-
-                mInjector.activityManagerOnUserStopped(userId);
-                // Clean up all state and processes associated with the user.
-                // Kill all the processes for the user.
-                forceStopUserLocked(userId, "finish user");
+                updateStartedUserArrayLU();
+                forceStopUser = true;
             }
         }
+        if (forceStopUser) {
+            mInjector.getUserManagerInternal().removeUserState(userId);
+            mInjector.activityManagerOnUserStopped(userId);
+            // Clean up all state and processes associated with the user.
+            // Kill all the processes for the user.
+            forceStopUser(userId, "finish user");
+        }
 
         for (int i = 0; i < callbacks.size(); i++) {
             try {
@@ -680,9 +703,7 @@
 
         if (stopped) {
             mInjector.systemServiceManagerCleanupUser(userId);
-            synchronized (mLock) {
-                mInjector.getActivityStackSupervisor().removeUserLocked(userId);
-            }
+            mInjector.stackSupervisorRemoveUser(userId);
             // Remove the user if it is ephemeral.
             if (getUserInfo(userId).isEphemeral()) {
                 mInjector.getUserManager().removeUser(userId);
@@ -700,39 +721,36 @@
      * Determines the list of users that should be stopped together with the specified
      * {@code userId}. The returned list includes {@code userId}.
      */
-    private @NonNull int[] getUsersToStopLocked(int userId) {
+    private @NonNull int[] getUsersToStopLU(int userId) {
         int startedUsersSize = mStartedUsers.size();
         IntArray userIds = new IntArray();
         userIds.add(userId);
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            int userGroupId = mUserProfileGroupIdsSelfLocked.get(userId,
+        int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+        for (int i = 0; i < startedUsersSize; i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            int startedUserId = uss.mHandle.getIdentifier();
+            // Skip unrelated users (profileGroupId mismatch)
+            int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
-            for (int i = 0; i < startedUsersSize; i++) {
-                UserState uss = mStartedUsers.valueAt(i);
-                int startedUserId = uss.mHandle.getIdentifier();
-                // Skip unrelated users (profileGroupId mismatch)
-                int startedUserGroupId = mUserProfileGroupIdsSelfLocked.get(startedUserId,
-                        UserInfo.NO_PROFILE_GROUP_ID);
-                boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
-                        && (userGroupId == startedUserGroupId);
-                // userId has already been added
-                boolean sameUserId = startedUserId == userId;
-                if (!sameGroup || sameUserId) {
-                    continue;
-                }
-                userIds.add(startedUserId);
+            boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
+                    && (userGroupId == startedUserGroupId);
+            // userId has already been added
+            boolean sameUserId = startedUserId == userId;
+            if (!sameGroup || sameUserId) {
+                continue;
             }
+            userIds.add(startedUserId);
         }
         return userIds.toArray();
     }
 
-    private void forceStopUserLocked(int userId, String reason) {
-        mInjector.activityManagerForceStopPackageLocked(userId, reason);
+    private void forceStopUser(int userId, String reason) {
+        mInjector.activityManagerForceStopPackage(userId, reason);
         Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_FOREGROUND);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        mInjector.broadcastIntentLocked(intent,
+        mInjector.broadcastIntent(intent,
                 null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                 null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
     }
@@ -741,6 +759,7 @@
      * Stops the guest or ephemeral user if it has gone to the background.
      */
     private void stopGuestOrEphemeralUserIfBackground() {
+        IntArray userIds = new IntArray();
         synchronized (mLock) {
             final int num = mUserLru.size();
             for (int i = 0; i < num; i++) {
@@ -751,28 +770,42 @@
                         || oldUss.state == UserState.STATE_SHUTDOWN) {
                     continue;
                 }
-                UserInfo userInfo = getUserInfo(oldUserId);
-                if (userInfo.isEphemeral()) {
-                    LocalServices.getService(UserManagerInternal.class)
-                            .onEphemeralUserStop(oldUserId);
+                userIds.add(oldUserId);
+            }
+        }
+        final int userIdsSize = userIds.size();
+        for (int i = 0; i < userIdsSize; i++) {
+            int oldUserId = userIds.get(i);
+            UserInfo userInfo = getUserInfo(oldUserId);
+            if (userInfo.isEphemeral()) {
+                LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
+            }
+            if (userInfo.isGuest() || userInfo.isEphemeral()) {
+                // This is a user to be stopped.
+                synchronized (mLock) {
+                    stopUsersLU(oldUserId, true, null);
                 }
-                if (userInfo.isGuest() || userInfo.isEphemeral()) {
-                    // This is a user to be stopped.
-                    stopUsersLocked(oldUserId, true, null);
-                    break;
-                }
+                break;
             }
         }
     }
 
-    void startProfilesLocked() {
+    void scheduleStartProfiles() {
+        if (!mHandler.hasMessages(START_PROFILES_MSG)) {
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
+                    DateUtils.SECOND_IN_MILLIS);
+        }
+    }
+
+    void startProfiles() {
+        int currentUserId = getCurrentUserId();
         if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
         List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
-                mCurrentUserId, false /* enabledOnly */);
+                currentUserId, false /* enabledOnly */);
         List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
         for (UserInfo user : profiles) {
             if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
-                    && user.id != mCurrentUserId && !user.isQuietModeEnabled()) {
+                    && user.id != currentUserId && !user.isQuietModeEnabled()) {
                 profilesToStart.add(user);
             }
         }
@@ -834,143 +867,156 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
+            final int oldUserId = getCurrentUserId();
+            if (oldUserId == userId) {
+                return true;
+            }
+
+            if (foreground) {
+                // TODO: I don't think this does what the caller think it does. Seems to only
+                // remove one locked task and won't work if multiple locked tasks are present.
+                mInjector.clearLockTaskMode("startUser");
+            }
+
+            final UserInfo userInfo = getUserInfo(userId);
+            if (userInfo == null) {
+                Slog.w(TAG, "No user info for user #" + userId);
+                return false;
+            }
+            if (foreground && userInfo.isManagedProfile()) {
+                Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+                return false;
+            }
+
+            if (foreground && mUserSwitchUiEnabled) {
+                mInjector.getWindowManager().startFreezingScreen(
+                        R.anim.screen_user_exit, R.anim.screen_user_enter);
+            }
+
+            boolean needStart = false;
+            boolean updateUmState = false;
+            UserState uss;
+
+            // If the user we are switching to is not currently started, then
+            // we need to start it now.
             synchronized (mLock) {
-                final int oldUserId = mCurrentUserId;
-                if (oldUserId == userId) {
-                    return true;
-                }
-
-                if (foreground) {
-                    // TODO: I don't think this does what the caller think it does. Seems to only
-                    // remove one locked task and won't work if multiple locked tasks are present.
-                    mInjector.getLockTaskController().clearLockTaskMode("startUser");
-                }
-
-                final UserInfo userInfo = getUserInfo(userId);
-                if (userInfo == null) {
-                    Slog.w(TAG, "No user info for user #" + userId);
-                    return false;
-                }
-                if (foreground && userInfo.isManagedProfile()) {
-                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
-                    return false;
-                }
-
-                if (foreground && mUserSwitchUiEnabled) {
-                    mInjector.getWindowManager().startFreezingScreen(
-                            R.anim.screen_user_exit, R.anim.screen_user_enter);
-                }
-
-                boolean needStart = false;
-
-                // If the user we are switching to is not currently started, then
-                // we need to start it now.
-                if (mStartedUsers.get(userId) == null) {
-                    UserState userState = new UserState(UserHandle.of(userId));
-                    mStartedUsers.put(userId, userState);
-                    mInjector.getUserManagerInternal().setUserState(userId, userState.state);
-                    updateStartedUserArrayLocked();
+                uss = mStartedUsers.get(userId);
+                if (uss == null) {
+                    uss = new UserState(UserHandle.of(userId));
+                    mStartedUsers.put(userId, uss);
+                    updateStartedUserArrayLU();
                     needStart = true;
+                    updateUmState = true;
                 }
-
-                final UserState uss = mStartedUsers.get(userId);
                 final Integer userIdInt = userId;
                 mUserLru.remove(userIdInt);
                 mUserLru.add(userIdInt);
-
-                if (foreground) {
+            }
+            if (updateUmState) {
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+            }
+            if (foreground) {
+                synchronized (mLock) {
                     mCurrentUserId = userId;
-                    mInjector.updateUserConfigurationLocked();
                     mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
-                    updateCurrentProfileIdsLocked();
-                    mInjector.getWindowManager().setCurrentUser(userId, mCurrentProfileIds);
-                    // Once the internal notion of the active user has switched, we lock the device
-                    // with the option to show the user switcher on the keyguard.
-                    if (mUserSwitchUiEnabled) {
-                        mInjector.getWindowManager().setSwitchingUser(true);
-                        mInjector.getWindowManager().lockNow(null);
-                    }
-                } else {
-                    final Integer currentUserIdInt = mCurrentUserId;
-                    updateCurrentProfileIdsLocked();
-                    mInjector.getWindowManager().setCurrentProfileIds(mCurrentProfileIds);
+                }
+                mInjector.updateUserConfiguration();
+                updateCurrentProfileIds();
+                mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
+                // Once the internal notion of the active user has switched, we lock the device
+                // with the option to show the user switcher on the keyguard.
+                if (mUserSwitchUiEnabled) {
+                    mInjector.getWindowManager().setSwitchingUser(true);
+                    mInjector.getWindowManager().lockNow(null);
+                }
+            } else {
+                final Integer currentUserIdInt = mCurrentUserId;
+                updateCurrentProfileIds();
+                mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
+                synchronized (mLock) {
                     mUserLru.remove(currentUserIdInt);
                     mUserLru.add(currentUserIdInt);
                 }
+            }
 
-                // Make sure user is in the started state.  If it is currently
-                // stopping, we need to knock that off.
-                if (uss.state == UserState.STATE_STOPPING) {
-                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
-                    // so we can just fairly silently bring the user back from
-                    // the almost-dead.
-                    uss.setState(uss.lastState);
-                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                } else if (uss.state == UserState.STATE_SHUTDOWN) {
-                    // This means ACTION_SHUTDOWN has been sent, so we will
-                    // need to treat this as a new boot of the user.
-                    uss.setState(UserState.STATE_BOOTING);
-                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                    updateStartedUserArrayLocked();
-                    needStart = true;
+            // Make sure user is in the started state.  If it is currently
+            // stopping, we need to knock that off.
+            if (uss.state == UserState.STATE_STOPPING) {
+                // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+                // so we can just fairly silently bring the user back from
+                // the almost-dead.
+                uss.setState(uss.lastState);
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+                synchronized (mLock) {
+                    updateStartedUserArrayLU();
                 }
-
-                if (uss.state == UserState.STATE_BOOTING) {
-                    // Give user manager a chance to propagate user restrictions
-                    // to other services and prepare app storage
-                    mInjector.getUserManager().onBeforeStartUser(userId);
-
-                    // Booting up a new user, need to tell system services about it.
-                    // Note that this is on the same handler as scheduling of broadcasts,
-                    // which is important because it needs to go first.
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+                needStart = true;
+            } else if (uss.state == UserState.STATE_SHUTDOWN) {
+                // This means ACTION_SHUTDOWN has been sent, so we will
+                // need to treat this as a new boot of the user.
+                uss.setState(UserState.STATE_BOOTING);
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+                synchronized (mLock) {
+                    updateStartedUserArrayLU();
                 }
+                needStart = true;
+            }
 
-                if (foreground) {
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
-                            oldUserId));
-                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
-                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
-                            oldUserId, userId, uss));
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
-                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
-                }
+            if (uss.state == UserState.STATE_BOOTING) {
+                // Give user manager a chance to propagate user restrictions
+                // to other services and prepare app storage
+                mInjector.getUserManager().onBeforeStartUser(userId);
 
-                if (needStart) {
-                    // Send USER_STARTED broadcast
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    mInjector.broadcastIntentLocked(intent,
-                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, SYSTEM_UID, userId);
-                }
+                // Booting up a new user, need to tell system services about it.
+                // Note that this is on the same handler as scheduling of broadcasts,
+                // which is important because it needs to go first.
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+            }
 
-                if (foreground) {
-                    moveUserToForegroundLocked(uss, oldUserId, userId);
-                } else {
-                    finishUserBoot(uss);
-                }
+            if (foreground) {
+                mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+                        oldUserId));
+                mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+                mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+                mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+                        oldUserId, userId, uss));
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+                        oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
+            }
 
-                if (needStart) {
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    mInjector.broadcastIntentLocked(intent,
-                            null, new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered, boolean sticky,
-                                        int sendingUser) throws RemoteException {
-                                }
-                            }, 0, null, null,
-                            new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
-                }
+            if (needStart) {
+                // Send USER_STARTED broadcast
+                Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                mInjector.broadcastIntent(intent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, false, false, MY_PID, SYSTEM_UID, userId);
+            }
+
+            if (foreground) {
+                moveUserToForeground(uss, oldUserId, userId);
+            } else {
+                finishUserBoot(uss);
+            }
+
+            if (needStart) {
+                Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                mInjector.broadcastIntent(intent,
+                        null, new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky,
+                                    int sendingUser) throws RemoteException {
+                            }
+                        }, 0, null, null,
+                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                        null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1014,7 +1060,7 @@
      * when the the credential-encrypted storage isn't tied to a user-provided
      * PIN or pattern.
      */
-    boolean maybeUnlockUser(final int userId) {
+    private boolean maybeUnlockUser(final int userId) {
         // Try unlocking storage using empty token
         return unlockUserCleared(userId, null, null, null);
     }
@@ -1027,65 +1073,106 @@
         }
     }
 
-    boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
+    private boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
             IProgressListener listener) {
         UserState uss;
-        synchronized (mLock) {
-            // TODO Move this block outside of synchronized if it causes lock contention
-            if (!StorageManager.isUserKeyUnlocked(userId)) {
-                final UserInfo userInfo = getUserInfo(userId);
-                final IStorageManager storageManager = getStorageManager();
-                try {
-                    // We always want to unlock user storage, even user is not started yet
-                    storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
-                } catch (RemoteException | RuntimeException e) {
-                    Slog.w(TAG, "Failed to unlock: " + e.getMessage());
-                }
+        if (!StorageManager.isUserKeyUnlocked(userId)) {
+            final UserInfo userInfo = getUserInfo(userId);
+            final IStorageManager storageManager = getStorageManager();
+            try {
+                // We always want to unlock user storage, even user is not started yet
+                storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
+            } catch (RemoteException | RuntimeException e) {
+                Slog.w(TAG, "Failed to unlock: " + e.getMessage());
             }
-            // Bail if user isn't actually running, otherwise register the given
-            // listener to watch for unlock progress
+        }
+        synchronized (mLock) {
+            // Register the given listener to watch for unlock progress
             uss = mStartedUsers.get(userId);
-            if (uss == null) {
-                notifyFinished(userId, listener);
-                return false;
-            } else {
+            if (uss != null) {
                 uss.mUnlockProgress.addListener(listener);
                 uss.tokenProvided = (token != null);
             }
         }
+        // Bail if user isn't actually running
+        if (uss == null) {
+            notifyFinished(userId, listener);
+            return false;
+        }
 
         finishUserUnlocking(uss);
 
-        final ArraySet<Integer> childProfilesToUnlock = new ArraySet<>();
-        synchronized (mLock) {
+        // We just unlocked a user, so let's now attempt to unlock any
+        // managed profiles under that user.
 
-            // We just unlocked a user, so let's now attempt to unlock any
-            // managed profiles under that user.
-            for (int i = 0; i < mStartedUsers.size(); i++) {
-                final int testUserId = mStartedUsers.keyAt(i);
-                final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
-                if (parent != null && parent.id == userId && testUserId != userId) {
-                    Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
-                            + "): attempting unlock because parent was just unlocked");
-                    childProfilesToUnlock.add(testUserId);
-                }
+        // First, get list of userIds. Requires mLock, so we cannot make external calls, e.g. to UMS
+        int[] userIds;
+        synchronized (mLock) {
+            userIds = new int[mStartedUsers.size()];
+            for (int i = 0; i < userIds.length; i++) {
+                userIds[i] = mStartedUsers.keyAt(i);
             }
         }
-
-        final int size = childProfilesToUnlock.size();
-        for (int i = 0; i < size; i++) {
-            maybeUnlockUser(childProfilesToUnlock.valueAt(i));
+        for (int testUserId : userIds) {
+            final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
+            if (parent != null && parent.id == userId && testUserId != userId) {
+                Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
+                        + "): attempting unlock because parent was just unlocked");
+                maybeUnlockUser(testUserId);
+            }
         }
 
         return true;
     }
 
-    void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
+    boolean switchUser(final int targetUserId) {
+        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
+        int currentUserId = getCurrentUserId();
+        UserInfo targetUserInfo = getUserInfo(targetUserId);
+        if (targetUserId == currentUserId) {
+            Slog.i(TAG, "user #" + targetUserId + " is already the current user");
+            return true;
+        }
+        if (targetUserInfo == null) {
+            Slog.w(TAG, "No user info for user #" + targetUserId);
+            return false;
+        }
+        if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mInjector.getContext())) {
+            Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
+                    + " when device is in demo mode");
+            return false;
+        }
+        if (!targetUserInfo.supportsSwitchTo()) {
+            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
+            return false;
+        }
+        if (targetUserInfo.isManagedProfile()) {
+            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
+            return false;
+        }
+        synchronized (mLock) {
+            mTargetUserId = targetUserId;
+        }
+        if (mUserSwitchUiEnabled) {
+            UserInfo currentUserInfo = getUserInfo(currentUserId);
+            Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
+            mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
+            mUiHandler.sendMessage(mHandler.obtainMessage(
+                    START_USER_SWITCH_UI_MSG, userNames));
+        } else {
+            mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    START_USER_SWITCH_FG_MSG, targetUserId, 0));
+        }
+        return true;
+    }
+
+    private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second);
     }
 
-    void dispatchForegroundProfileChanged(int userId) {
+    private void dispatchForegroundProfileChanged(int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1110,7 +1197,7 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    void dispatchLockedBootComplete(int userId) {
+    private void dispatchLockedBootComplete(int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1136,23 +1223,23 @@
         synchronized (mLock) {
             if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
                     + " and related users");
-            stopUsersLocked(oldUserId, false, null);
+            stopUsersLU(oldUserId, false, null);
         }
     }
 
-    void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+    private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         synchronized (mLock) {
             Slog.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
             mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
             mHandler.removeMessages(USER_SWITCH_CALLBACKS_TIMEOUT_MSG);
-            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+            sendContinueUserSwitchLU(uss, oldUserId, newUserId);
             // Report observers that never called back (USER_SWITCH_CALLBACKS_TIMEOUT)
             mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
                     oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
         }
     }
 
-    void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
+    private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
         synchronized (mLock) {
             if (mTimeoutUserSwitchCallbacks != null && !mTimeoutUserSwitchCallbacks.isEmpty()) {
                 Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId
@@ -1195,7 +1282,7 @@
                                 if (waitingCallbacksCount.decrementAndGet() == 0
                                         && (curWaitingUserSwitchCallbacks
                                         == mCurWaitingUserSwitchCallbacks)) {
-                                    sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                                    sendContinueUserSwitchLU(uss, oldUserId, newUserId);
                                 }
                             }
                         }
@@ -1206,25 +1293,23 @@
             }
         } else {
             synchronized (mLock) {
-                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                sendContinueUserSwitchLU(uss, oldUserId, newUserId);
             }
         }
         mUserSwitchObservers.finishBroadcast();
     }
 
-    void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
+    void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
         mCurWaitingUserSwitchCallbacks = null;
         mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-        mHandler.sendMessage(mHandler.obtainMessage(ActivityManagerService.CONTINUE_USER_SWITCH_MSG,
+        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
                 oldUserId, newUserId, uss));
     }
 
     void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
         Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
         if (mUserSwitchUiEnabled) {
-            synchronized (mLock) {
-                mInjector.getWindowManager().stopFreezingScreen();
-            }
+            mInjector.getWindowManager().stopFreezingScreen();
         }
         uss.switching = false;
         mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -1234,19 +1319,18 @@
         stopBackgroundUsersIfEnforced(oldUserId);
     }
 
-    void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
-        boolean homeInFront =
-                mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss);
+    private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
+        boolean homeInFront = mInjector.stackSupervisorSwitchUser(newUserId, uss);
         if (homeInFront) {
-            mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
+            mInjector.startHomeActivity(newUserId, "moveUserToForeground");
         } else {
-            mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
+            mInjector.stackSupervisorResumeFocusedStackTopActivity();
         }
         EventLogTags.writeAmSwitchUser(newUserId);
-        sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+        sendUserSwitchBroadcasts(oldUserId, newUserId);
     }
 
-    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+    void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
         long ident = Binder.clearCallingIdentity();
         try {
             Intent intent;
@@ -1260,7 +1344,7 @@
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND);
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    mInjector.broadcastIntentLocked(intent,
+                    mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                             null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                 }
@@ -1275,7 +1359,7 @@
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND);
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    mInjector.broadcastIntentLocked(intent,
+                    mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                             null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                 }
@@ -1283,7 +1367,7 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
                 intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
-                mInjector.broadcastIntentLocked(intent,
+                mInjector.broadcastIntent(intent,
                         null, null, 0, null, null,
                         new String[] {android.Manifest.permission.MANAGE_USERS},
                         AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
@@ -1308,7 +1392,7 @@
         // the value the caller will receive and someone else changing it.
         // We assume that USER_CURRENT_OR_SELF will use the current user; later
         // we will switch to the calling user if access to the current user fails.
-        int targetUserId = unsafeConvertIncomingUserLocked(userId);
+        int targetUserId = unsafeConvertIncomingUser(userId);
 
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             final boolean allow;
@@ -1376,9 +1460,9 @@
         return targetUserId;
     }
 
-    int unsafeConvertIncomingUserLocked(int userId) {
+    int unsafeConvertIncomingUser(int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
-                ? getCurrentUserIdLocked(): userId;
+                ? getCurrentUserId(): userId;
     }
 
     void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
@@ -1395,19 +1479,26 @@
         mUserSwitchObservers.register(observer, name);
     }
 
+    void sendForegroundProfileChanged(int userId) {
+        mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
+        mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, userId, 0).sendToTarget();
+    }
+
     void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
         mUserSwitchObservers.unregister(observer);
     }
 
-    UserState getStartedUserStateLocked(int userId) {
-        return mStartedUsers.get(userId);
+    UserState getStartedUserState(int userId) {
+        synchronized (mLock) {
+            return mStartedUsers.get(userId);
+        }
     }
 
     boolean hasStartedUserState(int userId) {
         return mStartedUsers.get(userId) != null;
     }
 
-    private void updateStartedUserArrayLocked() {
+    private void updateStartedUserArrayLU() {
         int num = 0;
         for (int i = 0; i < mStartedUsers.size(); i++) {
             UserState uss = mStartedUsers.valueAt(i);
@@ -1428,15 +1519,20 @@
         }
     }
 
-    void sendBootCompletedLocked(IIntentReceiver resultTo) {
-        for (int i = 0; i < mStartedUsers.size(); i++) {
-            UserState uss = mStartedUsers.valueAt(i);
+    void sendBootCompleted(IIntentReceiver resultTo) {
+        // Get a copy of mStartedUsers to use outside of lock
+        SparseArray<UserState> startedUsers;
+        synchronized (mLock) {
+            startedUsers = mStartedUsers.clone();
+        }
+        for (int i = 0; i < startedUsers.size(); i++) {
+            UserState uss = startedUsers.valueAt(i);
             finishUserBoot(uss, resultTo);
         }
     }
 
     void onSystemReady() {
-        updateCurrentProfileIdsLocked();
+        updateCurrentProfileIds();
     }
 
     /**
@@ -1444,33 +1540,35 @@
      * user switch happens or when a new related user is started in the
      * background.
      */
-    private void updateCurrentProfileIdsLocked() {
-        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(mCurrentUserId,
+    private void updateCurrentProfileIds() {
+        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(),
                 false /* enabledOnly */);
         int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
         for (int i = 0; i < currentProfileIds.length; i++) {
             currentProfileIds[i] = profiles.get(i).id;
         }
-        mCurrentProfileIds = currentProfileIds;
+        final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+        synchronized (mLock) {
+            mCurrentProfileIds = currentProfileIds;
 
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            mUserProfileGroupIdsSelfLocked.clear();
-            final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+            mUserProfileGroupIds.clear();
             for (int i = 0; i < users.size(); i++) {
                 UserInfo user = users.get(i);
                 if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
-                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+                    mUserProfileGroupIds.put(user.id, user.profileGroupId);
                 }
             }
         }
     }
 
-    int[] getStartedUserArrayLocked() {
-        return mStartedUserArray;
+    int[] getStartedUserArray() {
+        synchronized (mLock) {
+            return mStartedUserArray;
+        }
     }
 
-    boolean isUserRunningLocked(int userId, int flags) {
-        UserState state = getStartedUserStateLocked(userId);
+    boolean isUserRunning(int userId, int flags) {
+        UserState state = getStartedUserState(userId);
         if (state == null) {
             return false;
         }
@@ -1533,29 +1631,38 @@
             return getUserInfo(mCurrentUserId);
         }
         synchronized (mLock) {
-            return getCurrentUserLocked();
+            return getCurrentUserLU();
         }
     }
 
-    UserInfo getCurrentUserLocked() {
+    UserInfo getCurrentUserLU() {
         int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
         return getUserInfo(userId);
     }
 
-    int getCurrentOrTargetUserIdLocked() {
+    int getCurrentOrTargetUserId() {
+        synchronized (mLock) {
+            return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        }
+    }
+
+    int getCurrentOrTargetUserIdLU() {
         return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
     }
 
-    int getCurrentUserIdLocked() {
+
+    int getCurrentUserIdLU() {
         return mCurrentUserId;
     }
 
-    private boolean isCurrentUserLocked(int userId) {
-        return userId == getCurrentOrTargetUserIdLocked();
+    int getCurrentUserId() {
+        synchronized (mLock) {
+            return mCurrentUserId;
+        }
     }
 
-    int setTargetUserIdLocked(int targetUserId) {
-        return mTargetUserId = targetUserId;
+    private boolean isCurrentUserLU(int userId) {
+        return userId == getCurrentOrTargetUserIdLU();
     }
 
     int[] getUsers() {
@@ -1575,6 +1682,15 @@
         return mInjector.getUserManager().exists(userId);
     }
 
+    void enforceShellRestriction(String restriction, int userHandle) {
+        if (Binder.getCallingUid() == SHELL_UID) {
+            if (userHandle < 0 || hasUserRestriction(restriction, userHandle)) {
+                throw new SecurityException("Shell does not have permission to access user "
+                        + userHandle);
+            }
+        }
+    }
+
     boolean hasUserRestriction(String restriction, int userId) {
         return mInjector.getUserManager().hasUserRestriction(restriction, userId);
     }
@@ -1593,22 +1709,26 @@
         if (callingUserId == targetUserId) {
             return true;
         }
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+        synchronized (mLock) {
+            int callingProfile = mUserProfileGroupIds.get(callingUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
-            int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+            int targetProfile = mUserProfileGroupIds.get(targetUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
             return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
                     && callingProfile == targetProfile;
         }
     }
 
-    boolean isCurrentProfileLocked(int userId) {
-        return ArrayUtils.contains(mCurrentProfileIds, userId);
+    boolean isCurrentProfile(int userId) {
+        synchronized (mLock) {
+            return ArrayUtils.contains(mCurrentProfileIds, userId);
+        }
     }
 
-    int[] getCurrentProfileIdsLocked() {
-        return mCurrentProfileIds;
+    int[] getCurrentProfileIds() {
+        synchronized (mLock) {
+            return mCurrentProfileIds;
+        }
     }
 
     /**
@@ -1633,40 +1753,107 @@
     }
 
     void dump(PrintWriter pw, boolean dumpAll) {
-        pw.println("  mStartedUsers:");
-        for (int i = 0; i < mStartedUsers.size(); i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
-            pw.print(": "); uss.dump("", pw);
-        }
-        pw.print("  mStartedUserArray: [");
-        for (int i = 0; i < mStartedUserArray.length; i++) {
-            if (i > 0) pw.print(", ");
-            pw.print(mStartedUserArray[i]);
-        }
-        pw.println("]");
-        pw.print("  mUserLru: [");
-        for (int i = 0; i < mUserLru.size(); i++) {
-            if (i > 0) pw.print(", ");
-            pw.print(mUserLru.get(i));
-        }
-        pw.println("]");
-        if (dumpAll) {
-            pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
-        }
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+        synchronized (mLock) {
+            pw.println("  mStartedUsers:");
+            for (int i = 0; i < mStartedUsers.size(); i++) {
+                UserState uss = mStartedUsers.valueAt(i);
+                pw.print("    User #");
+                pw.print(uss.mHandle.getIdentifier());
+                pw.print(": ");
+                uss.dump("", pw);
+            }
+            pw.print("  mStartedUserArray: [");
+            for (int i = 0; i < mStartedUserArray.length; i++) {
+                if (i > 0)
+                    pw.print(", ");
+                pw.print(mStartedUserArray[i]);
+            }
+            pw.println("]");
+            pw.print("  mUserLru: [");
+            for (int i = 0; i < mUserLru.size(); i++) {
+                if (i > 0)
+                    pw.print(", ");
+                pw.print(mUserLru.get(i));
+            }
+            pw.println("]");
+            if (dumpAll) {
+                pw.print("  mStartedUserArray: ");
+                pw.println(Arrays.toString(mStartedUserArray));
+            }
+            if (mUserProfileGroupIds.size() > 0) {
                 pw.println("  mUserProfileGroupIds:");
-                for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+                for (int i=0; i< mUserProfileGroupIds.size(); i++) {
                     pw.print("    User #");
-                    pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+                    pw.print(mUserProfileGroupIds.keyAt(i));
                     pw.print(" -> profile #");
-                    pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+                    pw.println(mUserProfileGroupIds.valueAt(i));
                 }
             }
         }
     }
 
+    public boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case START_USER_SWITCH_FG_MSG:
+                startUserInForeground(msg.arg1);
+                break;
+            case REPORT_USER_SWITCH_MSG:
+                dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                break;
+            case CONTINUE_USER_SWITCH_MSG:
+                continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                break;
+            case USER_SWITCH_TIMEOUT_MSG:
+                timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                break;
+            case USER_SWITCH_CALLBACKS_TIMEOUT_MSG:
+                timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
+                break;
+            case START_PROFILES_MSG:
+                startProfiles();
+                break;
+            case SYSTEM_USER_START_MSG:
+                mInjector.batteryStatsServiceNoteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+                        Integer.toString(msg.arg1), msg.arg1);
+                mInjector.getSystemServiceManager().startUser(msg.arg1);
+                break;
+            case SYSTEM_USER_UNLOCK_MSG:
+                final int userId = msg.arg1;
+                mInjector.getSystemServiceManager().unlockUser(userId);
+                mInjector.loadUserRecents(userId);
+                if (userId == UserHandle.USER_SYSTEM) {
+                    mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+                }
+                mInjector.installEncryptionUnawareProviders(userId);
+                finishUserUnlocked((UserState) msg.obj);
+                break;
+            case SYSTEM_USER_CURRENT_MSG:
+                mInjector.batteryStatsServiceNoteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
+                        Integer.toString(msg.arg2), msg.arg2);
+                mInjector.batteryStatsServiceNoteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+                        Integer.toString(msg.arg1), msg.arg1);
+
+                mInjector.getSystemServiceManager().switchUser(msg.arg1);
+                break;
+            case FOREGROUND_PROFILE_CHANGED_MSG:
+                dispatchForegroundProfileChanged(msg.arg1);
+                break;
+            case REPORT_USER_SWITCH_COMPLETE_MSG:
+                dispatchUserSwitchComplete(msg.arg1);
+                break;
+            case REPORT_LOCKED_BOOT_COMPLETE_MSG:
+                dispatchLockedBootComplete(msg.arg1);
+                break;
+            case START_USER_SWITCH_UI_MSG:
+                showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
+                break;
+        }
+        return false;
+    }
+
     @VisibleForTesting
     static class Injector {
         private final ActivityManagerService mService;
@@ -1677,12 +1864,12 @@
             mService = service;
         }
 
-        protected Object getLock() {
-            return mService;
+        protected Handler getHandler(Handler.Callback callback) {
+            return new Handler(mService.mHandlerThread.getLooper(), callback);
         }
 
-        protected Handler getHandler() {
-            return mService.mHandler;
+        protected Handler getUiHandler(Handler.Callback callback) {
+            return new Handler(mService.mUiHandler.getLooper(), callback);
         }
 
         protected Context getContext() {
@@ -1693,13 +1880,16 @@
             return new LockPatternUtils(getContext());
         }
 
-        protected int broadcastIntentLocked(Intent intent, String resolvedType,
+        protected int broadcastIntent(Intent intent, String resolvedType,
                 IIntentReceiver resultTo, int resultCode, String resultData,
                 Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
                 boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
-            return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
-                    resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
-                    ordered, sticky, callingPid, callingUid, userId);
+            // TODO b/64165549 Verify that mLock is not held before calling AMS methods
+            synchronized (mService) {
+                return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
+                        resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
+                        ordered, sticky, callingPid, callingUid, userId);
+            }
         }
 
         int checkCallingPermission(String permission) {
@@ -1710,7 +1900,9 @@
             return mService.mWindowManager;
         }
         void activityManagerOnUserStopped(int userId) {
-            mService.onUserStoppedLocked(userId);
+            synchronized (mService) {
+                mService.onUserStoppedLocked(userId);
+            }
         }
 
         void systemServiceManagerCleanupUser(int userId) {
@@ -1740,14 +1932,14 @@
             mService.mBatteryStatsService.noteEvent(code, name, uid);
         }
 
-        void systemServiceManagerStopUser(int userId) {
-            mService.mSystemServiceManager.stopUser(userId);
-        }
-
         boolean isRuntimeRestarted() {
             return mService.mSystemServiceManager.isRuntimeRestarted();
         }
 
+        SystemServiceManager getSystemServiceManager() {
+            return mService.mSystemServiceManager;
+        }
+
         boolean isFirstBootOrUpgrade() {
             IPackageManager pm = AppGlobals.getPackageManager();
             try {
@@ -1766,9 +1958,11 @@
             }.sendNext();
         }
 
-        void activityManagerForceStopPackageLocked(int userId, String reason) {
-            mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
-                    userId, reason);
+        void activityManagerForceStopPackage(int userId, String reason) {
+            synchronized (mService) {
+                mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+                        userId, reason);
+            }
         };
 
         int checkComponentPermission(String permission, int pid, int uid, int owningUid,
@@ -1776,20 +1970,36 @@
             return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
         }
 
-        void startHomeActivityLocked(int userId, String reason) {
-            mService.startHomeActivityLocked(userId, reason);
+        protected void startHomeActivity(int userId, String reason) {
+            synchronized (mService) {
+                mService.startHomeActivityLocked(userId, reason);
+            }
         }
 
-        void updateUserConfigurationLocked() {
-            mService.updateUserConfigurationLocked();
+        void updateUserConfiguration() {
+            synchronized (mService) {
+                mService.updateUserConfigurationLocked();
+            }
         }
 
-        void clearBroadcastQueueForUserLocked(int userId) {
-            mService.clearBroadcastQueueForUserLocked(userId);
+        void clearBroadcastQueueForUser(int userId) {
+            synchronized (mService) {
+                mService.clearBroadcastQueueForUserLocked(userId);
+            }
         }
 
-        void enforceShellRestriction(String restriction, int userId) {
-            mService.enforceShellRestriction(restriction, userId);
+        void loadUserRecents(int userId) {
+            synchronized (mService) {
+                mService.mRecentTasks.loadUserRecentsLocked(userId);
+            }
+        }
+
+        void startPersistentApps(int matchFlags) {
+            mService.startPersistentApps(matchFlags);
+        }
+
+        void installEncryptionUnawareProviders(int userId) {
+            mService.installEncryptionUnawareProviders(userId);
         }
 
         void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser) {
@@ -1798,12 +2008,28 @@
             d.show();
         }
 
-        ActivityStackSupervisor getActivityStackSupervisor() {
-            return mService.mStackSupervisor;
+        void stackSupervisorRemoveUser(int userId) {
+            synchronized (mService) {
+                mService.mStackSupervisor.removeUserLocked(userId);
+            }
         }
 
-        LockTaskController getLockTaskController() {
-            return mService.mLockTaskController;
+        protected boolean stackSupervisorSwitchUser(int userId, UserState uss) {
+            synchronized (mService) {
+                return mService.mStackSupervisor.switchUserLocked(userId, uss);
+            }
+        }
+
+        protected void stackSupervisorResumeFocusedStackTopActivity() {
+            synchronized (mService) {
+                mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+            }
+        }
+
+        protected void clearLockTaskMode(String reason) {
+            synchronized (mService) {
+                mService.mLockTaskController.clearLockTaskMode(reason);
+            }
         }
     }
 }
diff --git a/com/android/server/am/UserState.java b/com/android/server/am/UserState.java
index 2e27387..d36d9cb 100644
--- a/com/android/server/am/UserState.java
+++ b/com/android/server/am/UserState.java
@@ -59,8 +59,10 @@
     /**
      * The last time that a provider was reported to usage stats as being brought to important
      * foreground procstate.
+     * <p><strong>Important: </strong>Only access this field when holding ActivityManagerService
+     * lock.
      */
-    public final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
+    final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
 
     public UserState(UserHandle handle) {
         mHandle = handle;
diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java
index 80b5477..a6aaaa6 100644
--- a/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2427,14 +2427,14 @@
             out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
         }
         if (widget.options != null) {
-            out.attribute(null, "min_width", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
-            out.attribute(null, "min_height", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
-            out.attribute(null, "max_width", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
-            out.attribute(null, "max_height", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
+            int minWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+            int minHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+            int maxWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
+            int maxHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
+            out.attribute(null, "min_width", Integer.toHexString((minWidth > 0) ? minWidth : 0));
+            out.attribute(null, "min_height", Integer.toHexString((minHeight > 0) ? minHeight : 0));
+            out.attribute(null, "max_width", Integer.toHexString((maxWidth > 0) ? maxWidth : 0));
+            out.attribute(null, "max_height", Integer.toHexString((maxHeight > 0) ? maxHeight : 0));
             out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
                     AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
         }
diff --git a/com/android/server/audio/AudioEventLogger.java b/com/android/server/audio/AudioEventLogger.java
index c96138f..9ebd75b 100644
--- a/com/android/server/audio/AudioEventLogger.java
+++ b/com/android/server/audio/AudioEventLogger.java
@@ -16,6 +16,8 @@
 
 package com.android.server.audio;
 
+import android.util.Log;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -47,6 +49,22 @@
         }
 
         /**
+         * Causes the string message for the event to appear in the logcat.
+         * Here is an example of how to create a new event (a StringEvent), adding it to the logger
+         * (an instance of AudioEventLogger) while also making it show in the logcat:
+         * <pre>
+         *     myLogger.log(
+         *         (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
+         * </pre>
+         * @param tag the tag for the android.util.Log.v
+         * @return the same instance of the event
+         */
+        public Event printLog(String tag) {
+            Log.i(tag, eventToString());
+            return this;
+        }
+
+        /**
          * Convert event to String.
          * This method is only called when the logger history is about to the dumped,
          * so this method is where expensive String conversions should be made, not when the Event
diff --git a/com/android/server/audio/AudioService.java b/com/android/server/audio/AudioService.java
index 91b1591..5eb2a8d 100644
--- a/com/android/server/audio/AudioService.java
+++ b/com/android/server/audio/AudioService.java
@@ -461,6 +461,8 @@
 
     // Forced device usage for communications
     private int mForcedUseForComm;
+    private int mForcedUseForCommExt; // External state returned by getters: always consistent
+                                      // with requests by setters
 
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
@@ -749,6 +751,9 @@
         // relies on audio policy having correct ranges for volume indexes.
         mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
 
+        mPlaybackMonitor =
+                new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
 
         mRecordMonitor = new RecordingActivityMonitor(mContext);
@@ -2544,13 +2549,15 @@
             }
         }
         int status = AudioSystem.AUDIO_STATUS_OK;
+        int actualMode;
         do {
+            actualMode = mode;
             if (mode == AudioSystem.MODE_NORMAL) {
                 // get new mode from client at top the list if any
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     hdlr = mSetModeDeathHandlers.get(0);
                     cb = hdlr.getBinder();
-                    mode = hdlr.getMode();
+                    actualMode = hdlr.getMode();
                     if (DEBUG_MODE) {
                         Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid="
                                 + hdlr.mPid);
@@ -2574,12 +2581,11 @@
                 hdlr.setMode(mode);
             }
 
-            if (mode != mMode) {
-                status = AudioSystem.setPhoneState(mode);
+            if (actualMode != mMode) {
+                status = AudioSystem.setPhoneState(actualMode);
                 if (status == AudioSystem.AUDIO_STATUS_OK) {
-                    if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + mode); }
-                    mMode = mode;
-                    mModeLogger.log(new PhoneStateEvent(caller, pid, mode));
+                    if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); }
+                    mMode = actualMode;
                 } else {
                     if (hdlr != null) {
                         mSetModeDeathHandlers.remove(hdlr);
@@ -2595,13 +2601,16 @@
         } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty());
 
         if (status == AudioSystem.AUDIO_STATUS_OK) {
-            if (mode != AudioSystem.MODE_NORMAL) {
+            if (actualMode != AudioSystem.MODE_NORMAL) {
                 if (mSetModeDeathHandlers.isEmpty()) {
                     Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
                 } else {
                     newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
                 }
             }
+            // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
+            mModeLogger.log(
+                    new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode));
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
             int device = getDeviceForStream(streamType);
             int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
@@ -2890,13 +2899,14 @@
             mForcedUseForComm = AudioSystem.FORCE_NONE;
         }
 
+        mForcedUseForCommExt = mForcedUseForComm;
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
                 AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
     }
 
     /** @see AudioManager#isSpeakerphoneOn() */
     public boolean isSpeakerphoneOn() {
-        return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER);
+        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
     }
 
     /** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -2904,6 +2914,13 @@
         if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
             return;
         }
+
+        // Only enable calls from system components
+        if (Binder.getCallingUid() >= FIRST_APPLICATION_UID) {
+            mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+            return;
+        }
+
         // for logging only
         final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
@@ -2913,11 +2930,21 @@
 
     public void setBluetoothScoOnInt(boolean on, String eventSource) {
         if (on) {
+            // do not accept SCO ON if SCO audio is not connected
+            synchronized(mScoClients) {
+                if ((mBluetoothHeadset != null) &&
+                    (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                             != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                    return;
+                }
+            }
             mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
         } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
             mForcedUseForComm = AudioSystem.FORCE_NONE;
         }
-
+        mForcedUseForCommExt = mForcedUseForComm;
+        AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
                 AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
@@ -2926,7 +2953,7 @@
 
     /** @see AudioManager#isBluetoothScoOn() */
     public boolean isBluetoothScoOn() {
-        return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
+        return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
     }
 
     /** @see AudioManager#setBluetoothA2dpOn(boolean) */
@@ -4134,7 +4161,8 @@
                     newDevice, AudioSystem.getOutputDeviceName(newDevice)));
         }
         synchronized (mConnectedDevices) {
-            if ((newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+            if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+                    && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
                     && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
                     && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
                     && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
@@ -6134,12 +6162,12 @@
     private int mSafeMediaVolumeIndex;
     // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
     // corresponding to a gain of -30 dBFS in audio flinger mixer.
-    // We remove -15 dBs from the theoretical -15dB to account for the EQ boost when bands are set
-    // to max gain.
+    // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
+    // amplification when both effects are on with all band gains at maximum.
     // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
     // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
     private int mSafeUsbMediaVolumeIndex;
-    private static final float SAFE_VOLUME_GAIN_DBFS = -30.0f;
+    private static final float SAFE_VOLUME_GAIN_DBFS = -37.0f;
     // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
     private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
                                                 AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
@@ -6952,7 +6980,7 @@
     //======================
     // Audio playback notification
     //======================
-    private final PlaybackActivityMonitor mPlaybackMonitor = new PlaybackActivityMonitor();
+    private final PlaybackActivityMonitor mPlaybackMonitor;
 
     public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
         final boolean isPrivileged =
diff --git a/com/android/server/audio/AudioServiceEvents.java b/com/android/server/audio/AudioServiceEvents.java
index 634c8c2..9d9e35b 100644
--- a/com/android/server/audio/AudioServiceEvents.java
+++ b/com/android/server/audio/AudioServiceEvents.java
@@ -26,20 +26,27 @@
 
     final static class PhoneStateEvent extends AudioEventLogger.Event {
         final String mPackage;
-        final int mPid;
-        final int mMode;
+        final int mOwnerPid;
+        final int mRequesterPid;
+        final int mRequestedMode;
+        final int mActualMode;
 
-        PhoneStateEvent(String callingPackage, int pid, int mode) {
+        PhoneStateEvent(String callingPackage, int requesterPid, int requestedMode,
+                        int ownerPid, int actualMode) {
             mPackage = callingPackage;
-            mPid = pid;
-            mMode = mode;
+            mRequesterPid = requesterPid;
+            mRequestedMode = requestedMode;
+            mOwnerPid = ownerPid;
+            mActualMode = actualMode;
         }
 
         @Override
         public String eventToString() {
-            return new StringBuilder("setMode(").append(AudioSystem.modeToString(mMode))
+            return new StringBuilder("setMode(").append(AudioSystem.modeToString(mRequestedMode))
                     .append(") from package=").append(mPackage)
-                    .append(" pid=").append(mPid).toString();
+                    .append(" pid=").append(mRequesterPid)
+                    .append(" selected mode=").append(AudioSystem.modeToString(mActualMode))
+                    .append(" by pid=").append(mOwnerPid).toString();
         }
     }
 
diff --git a/com/android/server/audio/MediaFocusControl.java b/com/android/server/audio/MediaFocusControl.java
index 7d742ff..c5f563c 100644
--- a/com/android/server/audio/MediaFocusControl.java
+++ b/com/android/server/audio/MediaFocusControl.java
@@ -89,6 +89,9 @@
         pw.println("\nMediaFocusControl dump time: "
                 + DateFormat.getTimeInstance().format(new Date()));
         dumpFocusStack(pw);
+        pw.println("\n");
+        // log
+        mEventLogger.dump(pw);
     }
 
     //=================================================================
@@ -120,6 +123,14 @@
     private final static Object mAudioFocusLock = new Object();
 
     /**
+     * Arbitrary maximum size of audio focus stack to prevent apps OOM'ing this process.
+     */
+    private static final int MAX_STACK_SIZE = 100;
+
+    private static final AudioEventLogger mEventLogger = new AudioEventLogger(50,
+            "focus commands as seen by MediaFocusControl");
+
+    /**
      * Discard the current audio focus owner.
      * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
      * focus), remove it from the stack, and clear the remote control display.
@@ -643,11 +654,14 @@
     protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
             int sdk) {
-        Log.i(TAG, " AudioFocus  requestAudioFocus() from uid/pid " + Binder.getCallingUid()
-                + "/" + Binder.getCallingPid()
-                + " clientId=" + clientId
-                + " req=" + focusChangeHint
-                + " flags=0x" + Integer.toHexString(flags));
+        mEventLogger.log((new AudioEventLogger.StringEvent(
+                "requestAudioFocus() from uid/pid " + Binder.getCallingUid()
+                    + "/" + Binder.getCallingPid()
+                    + " clientId=" + clientId + " callingPack=" + callingPackageName
+                    + " req=" + focusChangeHint
+                    + " flags=0x" + Integer.toHexString(flags)
+                    + " sdk=" + sdk))
+                .printLog(TAG));
         // we need a valid binder callback for clients
         if (!cb.pingBinder()) {
             Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -660,6 +674,11 @@
         }
 
         synchronized(mAudioFocusLock) {
+            if (mFocusStack.size() > MAX_STACK_SIZE) {
+                Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
+                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+            }
+
             boolean enteringRingOrCall = !mRingOrCallActive
                     & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
             if (enteringRingOrCall) { mRingOrCallActive = true; }
@@ -770,10 +789,12 @@
      * */
     protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
             String callingPackageName) {
-        // AudioAttributes are currently ignored, to be used for zones
-        Log.i(TAG, " AudioFocus  abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
-                + "/" + Binder.getCallingPid()
-                + " clientId=" + clientId);
+        // AudioAttributes are currently ignored, to be used for zones / a11y
+        mEventLogger.log((new AudioEventLogger.StringEvent(
+                "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+                    + "/" + Binder.getCallingPid()
+                    + " clientId=" + clientId))
+                .printLog(TAG));
         try {
             // this will take care of notifying the new focus owner if needed
             synchronized(mAudioFocusLock) {
diff --git a/com/android/server/audio/PlaybackActivityMonitor.java b/com/android/server/audio/PlaybackActivityMonitor.java
index d1a37af..6506cf7 100644
--- a/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,6 +17,8 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
@@ -90,7 +92,14 @@
     private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
             new HashMap<Integer, AudioPlaybackConfiguration>();
 
-    PlaybackActivityMonitor() {
+    private final Context mContext;
+    private int mSavedAlarmVolume = -1;
+    private final int mMaxAlarmVolume;
+    private int mPrivilegedAlarmActiveCount = 0;
+
+    PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+        mContext = context;
+        mMaxAlarmVolume = maxAlarmVolume;
         PlayMonitorClient.sListenerDeathMonitor = this;
         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
     }
@@ -105,7 +114,7 @@
             if (index >= 0) {
                 if (!disable) {
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        mEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
+                        sEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
                     }
                     mBannedUids.remove(index);
                     // nothing else to do, future playback requests from this uid are ok
@@ -116,7 +125,7 @@
                         checkBanPlayer(apc, uid);
                     }
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        mEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
+                        sEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
                     }
                     mBannedUids.add(new Integer(uid));
                 } // no else to handle, uid already not in list, so enabling again is no-op
@@ -151,7 +160,7 @@
                 new AudioPlaybackConfiguration(pic, newPiid,
                         Binder.getCallingUid(), Binder.getCallingPid());
         apc.init();
-        mEventLogger.log(new NewPlayerEvent(apc));
+        sEventLogger.log(new NewPlayerEvent(apc));
         synchronized(mPlayerLock) {
             mPlayers.put(newPiid, apc);
         }
@@ -163,7 +172,7 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                mEventLogger.log(new AudioAttrEvent(piid, attr));
+                sEventLogger.log(new AudioAttrEvent(piid, attr));
                 change = apc.handleAudioAttributesEvent(attr);
             } else {
                 Log.e(TAG, "Error updating audio attributes");
@@ -175,6 +184,38 @@
         }
     }
 
+    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 &&
+                    apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
+                    mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
+                            apc.getClientPid(), apc.getClientUid()) ==
+                            PackageManager.PERMISSION_GRANTED) {
+                if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+                        apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    if (mPrivilegedAlarmActiveCount++ == 0) {
+                        mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
+                                AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
+                        AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                                mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                    }
+                } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+                        apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    if (--mPrivilegedAlarmActiveCount == 0) {
+                        if (AudioSystem.getStreamVolumeIndex(
+                                AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
+                                mMaxAlarmVolume) {
+                            AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                                    mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     public void playerEvent(int piid, int event, int binderUid) {
         if (DEBUG) { Log.v(TAG, String.format("playerEvent(piid=%d, event=%d)", piid, event)); }
         final boolean change;
@@ -183,12 +224,12 @@
             if (apc == null) {
                 return;
             }
-            mEventLogger.log(new PlayerEvent(piid, event));
+            sEventLogger.log(new PlayerEvent(piid, event));
             if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                 for (Integer uidInteger: mBannedUids) {
                     if (checkBanPlayer(apc, uidInteger.intValue())) {
                         // player was banned, do not update its state
-                        mEventLogger.log(new AudioEventLogger.StringEvent(
+                        sEventLogger.log(new AudioEventLogger.StringEvent(
                                 "not starting piid:" + piid + " ,is banned"));
                         return;
                     }
@@ -200,6 +241,7 @@
             }
             if (checkConfigurationCaller(piid, apc, binderUid)) {
                 //TODO add generation counter to only update to the latest state
+                checkVolumeForPrivilegedAlarm(apc, event);
                 change = apc.handleStateEvent(event);
             } else {
                 Log.e(TAG, "Error handling event " + event);
@@ -216,7 +258,7 @@
 
     public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
         // no check on UID yet because this is only for logging at the moment
-        mEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+        sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
     }
 
     public void releasePlayer(int piid, int binderUid) {
@@ -224,10 +266,11 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                mEventLogger.log(new AudioEventLogger.StringEvent(
+                sEventLogger.log(new AudioEventLogger.StringEvent(
                         "releasing player piid:" + piid));
                 mPlayers.remove(new Integer(piid));
                 mDuckingManager.removeReleased(apc);
+                checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
                 apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
             }
         }
@@ -278,7 +321,7 @@
             }
             pw.println("\n");
             // log
-            mEventLogger.dump(pw);
+            sEventLogger.dump(pw);
         }
     }
 
@@ -456,7 +499,8 @@
                 }
                 if (mute) {
                     try {
-                        Log.v(TAG, "call: muting player" + piid + " uid:" + apc.getClientUid());
+                        sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
+                                + piid + " uid:" + apc.getClientUid())).printLog(TAG));
                         apc.getPlayerProxy().setVolume(0.0f);
                         mMutedPlayers.add(new Integer(piid));
                     } catch (Exception e) {
@@ -480,7 +524,8 @@
                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
                 if (apc != null) {
                     try {
-                        Log.v(TAG, "call: unmuting player" + piid + " uid:" + apc.getClientUid());
+                        sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
+                                + piid).printLog(TAG));
                         apc.getPlayerProxy().setVolume(1.0f);
                     } catch (Exception e) {
                         Log.e(TAG, "call: error unmuting player " + piid + " uid:"
@@ -669,8 +714,7 @@
                     return;
                 }
                 try {
-                    Log.v(TAG, "ducking (skipRamp=" + skipRamp + ") player piid:"
-                            + apc.getPlayerInterfaceId() + " uid:" + mUid);
+                    sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
                     apc.getPlayerProxy().applyVolumeShaper(
                             DUCK_VSHAPE,
                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -685,7 +729,8 @@
                     final AudioPlaybackConfiguration apc = players.get(piid);
                     if (apc != null) {
                         try {
-                            Log.v(TAG, "unducking player " + piid + " uid:" + mUid);
+                            sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
+                                    + piid)).printLog(TAG));
                             apc.getPlayerProxy().applyVolumeShaper(
                                     DUCK_ID,
                                     VolumeShaper.Operation.REVERSE);
@@ -772,7 +817,28 @@
         }
     }
 
-    private final static class AudioAttrEvent extends AudioEventLogger.Event {
+    private static final class DuckEvent extends AudioEventLogger.Event {
+        private final int mPlayerIId;
+        private final boolean mSkipRamp;
+        private final int mClientUid;
+        private final int mClientPid;
+
+        DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+            mPlayerIId = apc.getPlayerInterfaceId();
+            mSkipRamp = skipRamp;
+            mClientUid = apc.getClientUid();
+            mClientPid = apc.getClientPid();
+        }
+
+        @Override
+        public String eventToString() {
+            return new StringBuilder("ducking player piid:").append(mPlayerIId)
+                    .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
+                    .append(" skip ramp:").append(mSkipRamp).toString();
+        }
+    }
+
+    private static final class AudioAttrEvent extends AudioEventLogger.Event {
         private final int mPlayerIId;
         private final AudioAttributes mPlayerAttr;
 
@@ -787,6 +853,6 @@
         }
     }
 
-    private final AudioEventLogger mEventLogger = new AudioEventLogger(100,
+    private static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
             "playback activity as reported through PlayerBase");
 }
diff --git a/com/android/server/autofill/AutofillManagerService.java b/com/android/server/autofill/AutofillManagerService.java
index ddc819d..1f4161a 100644
--- a/com/android/server/autofill/AutofillManagerService.java
+++ b/com/android/server/autofill/AutofillManagerService.java
@@ -115,11 +115,24 @@
     private final SparseBooleanArray mDisabledUsers = new SparseBooleanArray();
 
     private final LocalLog mRequestsHistory = new LocalLog(20);
+    private final LocalLog mUiLatencyHistory = new LocalLog(20);
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                if (sDebug) Slog.d(TAG, "Close system dialogs");
+
+                // TODO(b/64940307): we need to destroy all sessions that are finished but showing
+                // Save UI because there is no way to show the Save UI back when the activity
+                // beneath it is brought back to top. Ideally, we should just hide the UI and
+                // bring it back when the activity resumes.
+                synchronized (mLock) {
+                    for (int i = 0; i < mServicesCache.size(); i++) {
+                        mServicesCache.valueAt(i).destroyFinishedSessionsLocked();
+                    }
+                }
+
                 mUi.hideAll(null);
             }
         }
@@ -294,7 +307,7 @@
         AutofillManagerServiceImpl service = mServicesCache.get(resolvedUserId);
         if (service == null) {
             service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory,
-                    resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
+                    mUiLatencyHistory, resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
             mServicesCache.put(userId, service);
         }
         return service;
@@ -650,7 +663,7 @@
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service == null) return false;
-                return Objects.equals(packageName, service.getPackageName());
+                return Objects.equals(packageName, service.getServicePackageName());
             }
         }
 
@@ -724,6 +737,8 @@
                 if (showHistory) {
                     pw.println("Requests history:");
                     mRequestsHistory.reverseDump(fd, pw, args);
+                    pw.println("UI latency history:");
+                    mUiLatencyHistory.reverseDump(fd, pw, args);
                 }
             } finally {
                 setDebugLocked(oldDebug);
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index f2f96f8..862070a 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -35,6 +35,7 @@
 import android.content.pm.ServiceInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
@@ -63,6 +64,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.HandlerCaller;
 import com.android.server.autofill.ui.AutoFillUI;
 
@@ -89,6 +92,7 @@
     private final Context mContext;
     private final Object mLock;
     private final AutoFillUI mUi;
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private RemoteCallbackList<IAutoFillManagerClient> mClients;
     private AutofillServiceInfo mInfo;
@@ -96,6 +100,8 @@
     private static final Random sRandom = new Random();
 
     private final LocalLog mRequestsHistory;
+    private final LocalLog mUiLatencyHistory;
+
     /**
      * Whether service was disabled for user due to {@link UserManager} restrictions.
      */
@@ -137,17 +143,19 @@
     private long mLastPrune = 0;
 
     AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
-            int userId, AutoFillUI ui, boolean disabled) {
+            LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
         mContext = context;
         mLock = lock;
         mRequestsHistory = requestsHistory;
+        mUiLatencyHistory = uiLatencyHistory;
         mUserId = userId;
         mUi = ui;
         updateLocked(disabled);
     }
 
+    @Nullable
     CharSequence getServiceName() {
-        final String packageName = getPackageName();
+        final String packageName = getServicePackageName();
         if (packageName == null) {
             return null;
         }
@@ -162,7 +170,8 @@
         }
     }
 
-    String getPackageName() {
+    @Nullable
+    String getServicePackageName() {
         final ComponentName serviceComponent = getServiceComponentName();
         if (serviceComponent != null) {
             return serviceComponent.getPackageName();
@@ -216,8 +225,10 @@
             if (serviceInfo != null) {
                 mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
                         serviceComponent, mUserId);
+                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) {
@@ -343,17 +354,31 @@
     }
 
     void disableOwnedAutofillServicesLocked(int uid) {
-        if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid != uid) {
+        Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
+        if (mInfo == null) return;
+
+        final ServiceInfo serviceInfo = mInfo.getServiceInfo();
+        if (serviceInfo.applicationInfo.uid != uid) {
+            Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
+                    + " instead of " + serviceInfo.applicationInfo.uid
+                    + " for service " + mInfo);
             return;
         }
+
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final String autoFillService = getComponentNameFromSettings();
-            if (mInfo.getServiceInfo().getComponentName().equals(
-                    ComponentName.unflattenFromString(autoFillService))) {
+            final ComponentName componentName = serviceInfo.getComponentName();
+            if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
+                mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
+                        componentName.getPackageName());
                 Settings.Secure.putStringForUser(mContext.getContentResolver(),
                         Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
                 destroySessionsLocked();
+            } else {
+                Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
+                        + serviceInfo + ") does not match Settings (" + autoFillService + ")");
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -377,7 +402,7 @@
 
         final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
                 sessionId, uid, activityToken, appCallbackToken, hasCallback,
-                mInfo.getServiceInfo().getComponentName(), packageName);
+                mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName);
         mSessions.put(newSession.id, newSession);
 
         return newSession;
@@ -450,7 +475,7 @@
             final int sessionCount = mSessions.size();
             for (int i = sessionCount - 1; i >= 0; i--) {
                 final Session session = mSessions.valueAt(i);
-                if (session.isSaveUiPendingForToken(token)) {
+                if (session.isSaveUiPendingForTokenLocked(token)) {
                     session.onPendingSaveUi(operation, token);
                     return;
                 }
@@ -636,7 +661,7 @@
 
     void destroySessionsLocked() {
         if (mSessions.size() == 0) {
-            mUi.destroyAll(null, null);
+            mUi.destroyAll(null, null, false);
             return;
         }
         while (mSessions.size() > 0) {
@@ -644,6 +669,18 @@
         }
     }
 
+    // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
+    void destroyFinishedSessionsLocked() {
+        final int sessionCount = mSessions.size();
+        for (int i = sessionCount - 1; i >= 0; i--) {
+            final Session session = mSessions.valueAt(i);
+            if (session.isSavingLocked()) {
+                if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
+                session.forceRemoveSelfLocked();
+            }
+        }
+    }
+
     void listSessionsLocked(ArrayList<String> output) {
         final int numSessions = mSessions.size();
         for (int i = 0; i < numSessions; i++) {
diff --git a/com/android/server/autofill/Helper.java b/com/android/server/autofill/Helper.java
index 086dd29..236fbfd 100644
--- a/com/android/server/autofill/Helper.java
+++ b/com/android/server/autofill/Helper.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.service.autofill.Dataset;
 import android.util.ArrayMap;
@@ -25,6 +26,8 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
@@ -99,4 +102,14 @@
         }
         return fields;
     }
+
+    @NonNull
+    public static LogMaker newLogMaker(int category, String packageName,
+            String servicePackageName) {
+        final LogMaker log = new LogMaker(category).setPackageName(packageName);
+        if (servicePackageName != null) {
+            log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        }
+        return log;
+    }
 }
diff --git a/com/android/server/autofill/RemoteFillService.java b/com/android/server/autofill/RemoteFillService.java
index f315148..af55807 100644
--- a/com/android/server/autofill/RemoteFillService.java
+++ b/com/android/server/autofill/RemoteFillService.java
@@ -578,9 +578,8 @@
         public void run() {
             synchronized (mLock) {
                 if (isCancelledLocked()) {
-                    // TODO(b/653742740): we should probably return here, but for now we're justing
-                    // logging to confirm this is the problem if it happens again.
-                    Slog.e(LOG_TAG, "run() called after canceled: " + mRequest);
+                    if (sDebug) Slog.d(LOG_TAG, "run() called after canceled: " + mRequest);
+                    return;
                 }
             }
             final RemoteFillService remoteService = getService();
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index 171053f..ed00ffe 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -50,19 +50,24 @@
 import android.os.IBinder;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillContext;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
+import android.service.autofill.InternalSanitizer;
 import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
 import android.service.autofill.SaveRequest;
+import android.service.autofill.Transformation;
 import android.service.autofill.ValueFinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
@@ -183,6 +188,20 @@
     private ArrayList<String> mSelectedDatasetIds;
 
     /**
+     * When the session started (using elapsed time since boot).
+     */
+    private final long mStartTime;
+
+    /**
+     * When the UI was shown for the first time (using elapsed time since boot).
+     */
+    @GuardedBy("mLock")
+    private long mUiShownTime;
+
+    @GuardedBy("mLock")
+    private final LocalLog mUiLatencyHistory;
+
+    /**
      * Receiver of assist data from the app's {@link Activity}.
      */
     private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
@@ -403,10 +422,11 @@
     Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
             @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
             @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
-            @NonNull IBinder client, boolean hasCallback,
+            @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
             @NonNull ComponentName componentName, @NonNull String packageName) {
         id = sessionId;
         this.uid = uid;
+        mStartTime = SystemClock.elapsedRealtime();
         mService = service;
         mLock = lock;
         mUi = ui;
@@ -414,10 +434,11 @@
         mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
         mActivityToken = activityToken;
         mHasCallback = hasCallback;
+        mUiLatencyHistory = uiLatencyHistory;
         mPackageName = packageName;
         mClient = IAutoFillManagerClient.Stub.asInterface(client);
 
-        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
+        writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED);
     }
 
     /**
@@ -471,19 +492,16 @@
         if ((response.getDatasets() == null || response.getDatasets().isEmpty())
                         && response.getAuthentication() == null) {
             // Response is "empty" from an UI point of view, need to notify client.
-            notifyUnavailableToClient();
+            notifyUnavailableToClient(false);
         }
         synchronized (mLock) {
             processResponseLocked(response, requestFlags);
         }
 
-        final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
                 .setType(MetricsEvent.TYPE_SUCCESS)
-                .setPackageName(mPackageName)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
-                        response.getDatasets() == null ? 0 : response.getDatasets().size())
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
-                        servicePackageName);
+                        response.getDatasets() == null ? 0 : response.getDatasets().size());
         mMetricsLogger.write(log);
     }
 
@@ -499,10 +517,8 @@
             }
             mService.resetLastResponse();
         }
-        LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
-                .setType(MetricsEvent.TYPE_FAILURE)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_FAILURE);
         mMetricsLogger.write(log);
 
         getUiForShowing().showError(message, this);
@@ -521,11 +537,8 @@
                 return;
             }
         }
-        LogMaker log = (new LogMaker(
-                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                .setType(MetricsEvent.TYPE_SUCCESS)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_SUCCESS);
         mMetricsLogger.write(log);
 
         // Nothing left to do...
@@ -545,11 +558,8 @@
                 return;
             }
         }
-        LogMaker log = (new LogMaker(
-                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                .setType(MetricsEvent.TYPE_FAILURE)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_FAILURE);
         mMetricsLogger.write(log);
 
         getUiForShowing().showError(message, this);
@@ -583,6 +593,10 @@
     // FillServiceCallbacks
     @Override
     public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
+        if (sDebug) {
+            Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
+                    + "; intentSender=" + intent);
+        }
         final Intent fillInIntent;
         synchronized (mLock) {
             if (mDestroyed) {
@@ -591,6 +605,10 @@
                 return;
             }
             fillInIntent = createAuthFillInIntentLocked(requestId, extras);
+            if (fillInIntent == null) {
+                forceRemoveSelfLocked();
+                return;
+            }
         }
 
         mService.setAuthenticationSelected(id, mClientState);
@@ -746,21 +764,22 @@
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
         if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
         if (result instanceof FillResponse) {
-            final FillResponse response = (FillResponse) result;
-            mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
-            replaceResponseLocked(authenticatedResponse, response);
+            writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED);
+            replaceResponseLocked(authenticatedResponse, (FillResponse) result);
         } else if (result instanceof Dataset) {
-            // TODO: add proper metric
             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
+                writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
                 final Dataset dataset = (Dataset) result;
                 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
                 autoFill(requestId, datasetIdx, dataset, false);
+            } else {
+                writeLog(MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
             }
         } else {
             if (result != null) {
                 Slog.w(TAG, "service returned invalid auth type: " + result);
             }
-            // TODO: add proper metric (on else)
+            writeLog(MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
             processNullResponseLocked(0);
         }
     }
@@ -774,6 +793,44 @@
         mHasCallback = hasIt;
     }
 
+    @Nullable
+    private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
+        if (mContexts == null) {
+            if (sDebug && logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
+            return null;
+        }
+        if (mResponses == null) {
+            // Happens when the activity / session was finished before the service replied, or
+            // when the service cannot autofill it (and returned a null response).
+            if (sVerbose && logPrefix != null) {
+                Slog.v(TAG, logPrefix + ": no responses on session");
+            }
+            return null;
+        }
+
+        final int lastResponseIdx = getLastResponseIndexLocked();
+        if (lastResponseIdx < 0) {
+            if (logPrefix != null) {
+                Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
+                        + ", mViewStates=" + mViewStates);
+            }
+            return null;
+        }
+
+        final FillResponse response = mResponses.valueAt(lastResponseIdx);
+        if (sVerbose && logPrefix != null) {
+            Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
+                    + ", mViewStates=" + mViewStates);
+        }
+        return response;
+    }
+
+    @Nullable
+    private SaveInfo getSaveInfoLocked() {
+        final FillResponse response = getLastResponseLocked(null);
+        return response == null ? null : response.getSaveInfo();
+    }
+
     /**
      * Shows the save UI, when session can be saved.
      *
@@ -785,32 +842,8 @@
                     + id + " destroyed");
             return false;
         }
-        if (mContexts == null) {
-            Slog.d(TAG, "showSaveLocked(): no contexts");
-            return true;
-        }
-        if (mResponses == null) {
-            // Happens when the activity / session was finished before the service replied, or
-            // when the service cannot autofill it (and returned a null response).
-            if (sVerbose) {
-                Slog.v(TAG, "showSaveLocked(): no responses on session");
-            }
-            return true;
-        }
-
-        final int lastResponseIdx = getLastResponseIndexLocked();
-        if (lastResponseIdx < 0) {
-            Slog.w(TAG, "showSaveLocked(): did not get last response. mResponses=" + mResponses
-                    + ", mViewStates=" + mViewStates);
-            return true;
-        }
-
-        final FillResponse response = mResponses.valueAt(lastResponseIdx);
-        final SaveInfo saveInfo = response.getSaveInfo();
-        if (sVerbose) {
-            Slog.v(TAG, "showSaveLocked(): mResponses=" + mResponses + ", mContexts=" + mContexts
-                    + ", mViewStates=" + mViewStates);
-        }
+        final FillResponse response = getLastResponseLocked("showSaveLocked()");
+        final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
 
         /*
          * The Save dialog is only shown if all conditions below are met:
@@ -825,6 +858,8 @@
             return true;
         }
 
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo);
+
         // Cache used to make sure changed fields do not belong to a dataset.
         final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
         final ArraySet<AutofillId> allIds = new ArraySet<>();
@@ -864,6 +899,7 @@
                         break;
                     }
                 }
+                value = getSanitizedValue(sanitizers, id, value);
                 currentValues.put(id, value);
                 final AutofillValue filledValue = viewState.getAutofilledValue();
 
@@ -922,15 +958,21 @@
 
                 final InternalValidator validator = saveInfo.getValidator();
                 if (validator != null) {
+                    final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION);
                     boolean isValid;
                     try {
                         isValid = validator.isValid(valueFinder);
+                        log.setType(isValid
+                                ? MetricsEvent.TYPE_SUCCESS
+                                : MetricsEvent.TYPE_DISMISS);
                     } catch (Exception e) {
-                        Slog.e(TAG, "Not showing save UI because of exception during validation "
-                                + e.getClass());
+                        Slog.e(TAG, "Not showing save UI because validation failed:", e);
+                        log.setType(MetricsEvent.TYPE_FAILURE);
+                        mMetricsLogger.write(log);
                         return true;
                     }
 
+                    mMetricsLogger.write(log);
                     if (!isValid) {
                         Slog.i(TAG, "not showing save UI because fields failed validation");
                         return true;
@@ -978,7 +1020,8 @@
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
-                        saveInfo, valueFinder, mPackageName, this, mPendingSaveUi);
+                        mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this,
+                        mPendingSaveUi);
                 if (client != null) {
                     try {
                         client.setSaveUiState(id, true);
@@ -999,6 +1042,48 @@
         return true;
     }
 
+    @Nullable
+    private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
+        if (saveInfo == null) return null;
+
+        final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys();
+        if (sanitizerKeys == null) return null;
+
+        final int size = sanitizerKeys.length ;
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size);
+        if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers");
+        final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues();
+        for (int i = 0; i < size; i++) {
+            final InternalSanitizer sanitizer = sanitizerKeys[i];
+            final AutofillId[] ids = sanitizerValues[i];
+            if (sDebug) {
+                Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids "
+                        + Arrays.toString(ids));
+            }
+            for (AutofillId id : ids) {
+                sanitizers.put(id, sanitizer);
+            }
+        }
+        return sanitizers;
+    }
+
+    @NonNull
+    private AutofillValue getSanitizedValue(
+            @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
+            @NonNull AutofillId id,
+            @NonNull AutofillValue value) {
+        if (sanitizers == null) return value;
+
+        final InternalSanitizer sanitizer = sanitizers.get(id);
+        if (sanitizer == null) {
+            return value;
+        }
+
+        final AutofillValue sanitized = sanitizer.sanitize(value);
+        if (sDebug) Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized);
+        return sanitized;
+    }
+
     /**
      * Returns whether the session is currently showing the save UI
      */
@@ -1062,6 +1147,9 @@
             return;
         }
 
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers =
+                createSanitizers(getSaveInfoLocked());
+
         final int numContexts = mContexts.size();
 
         for (int contextNum = 0; contextNum < numContexts; contextNum++) {
@@ -1088,7 +1176,9 @@
                 }
                 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
 
-                node.updateAutofillValue(value);
+                final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+
+                node.updateAutofillValue(sanitizedValue);
             }
 
             // Sanitize structure before it's sent to service.
@@ -1244,6 +1334,21 @@
                 break;
             case ACTION_VALUE_CHANGED:
                 if (value != null && !value.equals(viewState.getCurrentValue())) {
+                    if (value.isEmpty()
+                            && viewState.getCurrentValue() != null
+                            && viewState.getCurrentValue().isText()
+                            && viewState.getCurrentValue().getTextValue() != null
+                            && getSaveInfoLocked() != null) {
+                        final int length = viewState.getCurrentValue().getTextValue().length();
+                        if (sDebug) {
+                            Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
+                                    + length + " chars long");
+                        }
+                        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
+                                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
+                        mMetricsLogger.write(log);
+                    }
+
                     // Always update the internal state.
                     viewState.setCurrentValue(value);
 
@@ -1319,7 +1424,33 @@
             filterText = value.getTextValue().toString();
         }
 
-        getUiForShowing().showFillUi(filledId, response, filterText, mPackageName, this);
+        getUiForShowing().showFillUi(filledId, response, filterText,
+                mService.getServicePackageName(), mPackageName, this);
+
+        synchronized (mLock) {
+            if (mUiShownTime == 0) {
+                // Log first time UI is shown.
+                mUiShownTime = SystemClock.elapsedRealtime();
+                final long duration = mUiShownTime - mStartTime;
+                if (sDebug) {
+                    final StringBuilder msg = new StringBuilder("1st UI for ")
+                            .append(mActivityToken)
+                            .append(" shown in ");
+                    TimeUtils.formatDuration(duration, msg);
+                    Slog.d(TAG, msg.toString());
+                }
+                final StringBuilder historyLog = new StringBuilder("id=").append(id)
+                        .append(" app=").append(mActivityToken)
+                        .append(" svc=").append(mService.getServicePackageName())
+                        .append(" latency=");
+                TimeUtils.formatDuration(duration, historyLog);
+                mUiLatencyHistory.log(historyLog.toString());
+
+                final LogMaker metricsLog = newLogMaker(MetricsEvent.AUTOFILL_UI_LATENCY)
+                        .setCounterValue((int) duration);
+                mMetricsLogger.write(metricsLog);
+            }
+        }
     }
 
     boolean isDestroyed() {
@@ -1334,11 +1465,15 @@
         }
     }
 
-    private void notifyUnavailableToClient() {
+    private void notifyUnavailableToClient(boolean sessionFinished) {
         synchronized (mLock) {
-            if (!mHasCallback || mCurrentViewId == null) return;
+            if (mCurrentViewId == null) return;
             try {
-                mClient.notifyNoFillUi(id, mCurrentViewId);
+                if (mHasCallback) {
+                    mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished);
+                } else if (sessionFinished) {
+                    mClient.setSessionFinished(AutofillManager.STATE_FINISHED);
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
             }
@@ -1426,7 +1561,7 @@
         }
         mService.resetLastResponse();
         // Nothing to be done, but need to notify client.
-        notifyUnavailableToClient();
+        notifyUnavailableToClient(true);
         removeSelf();
     }
 
@@ -1545,6 +1680,10 @@
     }
 
     void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
+        if (sDebug) {
+            Slog.d(TAG, "autoFill(): requestId=" + requestId  + "; datasetIdx=" + datasetIndex
+                    + "; dataset=" + dataset);
+        }
         synchronized (mLock) {
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
@@ -1565,10 +1704,14 @@
             mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
             final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
-
+            if (fillInIntent == null) {
+                forceRemoveSelfLocked();
+                return;
+            }
             final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
                     datasetIndex);
             startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
+
         }
     }
 
@@ -1578,14 +1721,16 @@
         }
     }
 
+    // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
+    @Nullable
     private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
         final Intent fillInIntent = new Intent();
 
         final FillContext context = getFillContextByRequestIdLocked(requestId);
         if (context == null) {
-            // TODO(b/653742740): this will crash system_server. We need to handle it, but we're
-            // keeping it crashing for now so we can diagnose when it happens again
-            Slog.wtf(TAG, "no FillContext for requestId" + requestId + "; mContexts= " + mContexts);
+            Slog.wtf(TAG, "createAuthFillInIntentLocked(): no FillContext. requestId=" + requestId
+                    + "; mContexts= " + mContexts);
+            return null;
         }
         fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
@@ -1614,6 +1759,14 @@
         pw.print(prefix); pw.print("uid: "); pw.println(uid);
         pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
         pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
+        pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
+        pw.print(prefix); pw.print("Time to show UI: ");
+        if (mUiShownTime == 0) {
+            pw.println("N/A");
+        } else {
+            TimeUtils.formatDuration(mUiShownTime - mStartTime, pw);
+            pw.println();
+        }
         pw.print(prefix); pw.print("mResponses: ");
         if (mResponses == null) {
             pw.println("null");
@@ -1732,10 +1885,10 @@
         if (mDestroyed) {
             return null;
         }
-        mUi.destroyAll(mPendingSaveUi, this);
+        mUi.destroyAll(mPendingSaveUi, this, true);
         mUi.clearCallback(this);
         mDestroyed = true;
-        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
+        writeLog(MetricsEvent.AUTOFILL_SESSION_FINISHED);
         return mRemoteFillService;
     }
 
@@ -1746,9 +1899,17 @@
     void forceRemoveSelfLocked() {
         if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
 
+        final boolean isPendingSaveUi = isSaveUiPendingLocked();
         mPendingSaveUi = null;
         removeSelfLocked();
-        mUi.destroyAll(mPendingSaveUi, this);
+        mUi.destroyAll(mPendingSaveUi, this, false);
+        if (!isPendingSaveUi) {
+            try {
+                mClient.setSessionFinished(AutofillManager.STATE_UNKNOWN);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error notifying client to finish session", e);
+            }
+        }
     }
 
     /**
@@ -1771,7 +1932,7 @@
                     + id + " destroyed");
             return;
         }
-        if (isSaveUiPending()) {
+        if (isSaveUiPendingLocked()) {
             Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
             return;
         }
@@ -1792,14 +1953,14 @@
      * a specific {@code token} created by
      * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
      */
-    boolean isSaveUiPendingForToken(@NonNull IBinder token) {
-        return isSaveUiPending() && token.equals(mPendingSaveUi.getToken());
+    boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
+        return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
     }
 
     /**
      * Checks whether this session is hiding the Save UI to handle a custom description link.
      */
-    private boolean isSaveUiPending() {
+    private boolean isSaveUiPendingLocked() {
         return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
     }
 
@@ -1820,4 +1981,16 @@
         }
         return lastResponseIdx;
     }
+
+    private LogMaker newLogMaker(int category) {
+        return newLogMaker(category, mService.getServicePackageName());
+    }
+
+    private LogMaker newLogMaker(int category, String servicePackageName) {
+        return Helper.newLogMaker(category, mPackageName, servicePackageName);
+    }
+
+    private void writeLog(int category) {
+        mMetricsLogger.write(newLogMaker(category));
+    }
 }
diff --git a/com/android/server/autofill/ui/AutoFillUI.java b/com/android/server/autofill/ui/AutoFillUI.java
index cac2bff..36b95fc 100644
--- a/com/android/server/autofill/ui/AutoFillUI.java
+++ b/com/android/server/autofill/ui/AutoFillUI.java
@@ -40,8 +40,9 @@
 import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
 
 import java.io.PrintWriter;
 
@@ -158,21 +159,22 @@
      * @param focusedId the currently focused field
      * @param response the current fill response
      * @param filterText text of the view to be filled
+     * @param servicePackageName package name of the autofill service filling the activity
      * @param packageName package name of the activity that is filled
      * @param callback Identifier for the caller
      */
     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
-            @Nullable String filterText, @NonNull String packageName,
-            @NonNull AutoFillUiCallback callback) {
+            @Nullable String filterText, @Nullable String servicePackageName,
+            @NonNull String packageName, @NonNull AutoFillUiCallback callback) {
         if (sDebug) {
             final int size = filterText == null ? 0 : filterText.length();
             Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars");
         }
-        final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
-                .setPackageName(packageName)
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
+        final LogMaker log =
+                Helper.newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
                         filterText == null ? 0 : filterText.length())
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
                         response.getDatasets() == null ? 0 : response.getDatasets().size());
 
         mHandler.post(() -> {
@@ -184,7 +186,7 @@
                     filterText, mOverlayControl, new FillUi.Callback() {
                 @Override
                 public void onResponsePicked(FillResponse response) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
+                    log.setType(MetricsEvent.TYPE_DETAIL);
                     hideFillUiUiThread(callback);
                     if (mCallback != null) {
                         mCallback.authenticate(response.getRequestId(),
@@ -195,7 +197,7 @@
 
                 @Override
                 public void onDatasetPicked(Dataset dataset) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+                    log.setType(MetricsEvent.TYPE_ACTION);
                     hideFillUiUiThread(callback);
                     if (mCallback != null) {
                         final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -205,14 +207,14 @@
 
                 @Override
                 public void onCanceled() {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+                    log.setType(MetricsEvent.TYPE_DISMISS);
                     hideFillUiUiThread(callback);
                 }
 
                 @Override
                 public void onDestroy() {
-                    if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
-                        log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+                    if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+                        log.setType(MetricsEvent.TYPE_CLOSE);
                     }
                     mMetricsLogger.write(log);
                 }
@@ -246,37 +248,39 @@
      * Shows the UI asking the user to save for autofill.
      */
     public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
-            @NonNull SaveInfo info,@NonNull ValueFinder valueFinder, @NonNull String packageName,
+            @Nullable String servicePackageName, @NonNull SaveInfo info,
+            @NonNull ValueFinder valueFinder, @NonNull String packageName,
             @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) {
         if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
         int numIds = 0;
         numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
         numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
 
-        LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_SAVE_UI))
-                .setPackageName(packageName).addTaggedData(
-                        MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
+        final LogMaker log =
+                Helper.newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
 
         mHandler.post(() -> {
             if (callback != mCallback) {
                 return;
             }
             hideAllUiThread(callback);
-            mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon, info,
-                    valueFinder, mOverlayControl, new SaveUi.OnSaveListener() {
+            mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon,
+                    servicePackageName, packageName, info, valueFinder, mOverlayControl,
+                    new SaveUi.OnSaveListener() {
                 @Override
                 public void onSave() {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+                    log.setType(MetricsEvent.TYPE_ACTION);
                     hideSaveUiUiThread(mCallback);
                     if (mCallback != null) {
                         mCallback.save();
                     }
-                    destroySaveUiUiThread(pendingSaveUi);
+                    destroySaveUiUiThread(pendingSaveUi, true);
                 }
 
                 @Override
                 public void onCancel(IntentSender listener) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+                    log.setType(MetricsEvent.TYPE_DISMISS);
                     hideSaveUiUiThread(mCallback);
                     if (listener != null) {
                         try {
@@ -289,13 +293,13 @@
                     if (mCallback != null) {
                         mCallback.cancelSave();
                     }
-                    destroySaveUiUiThread(pendingSaveUi);
+                    destroySaveUiUiThread(pendingSaveUi, true);
                 }
 
                 @Override
                 public void onDestroy() {
-                    if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
-                        log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+                    if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+                        log.setType(MetricsEvent.TYPE_CLOSE);
 
                         if (mCallback != null) {
                             mCallback.cancelSave();
@@ -331,8 +335,8 @@
      * Destroy all UI affordances.
      */
     public void destroyAll(@Nullable PendingUi pendingSaveUi,
-            @Nullable AutoFillUiCallback callback) {
-        mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback));
+            @Nullable AutoFillUiCallback callback, boolean notifyClient) {
+        mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback, notifyClient));
     }
 
     public void dump(PrintWriter pw) {
@@ -375,7 +379,7 @@
     }
 
     @android.annotation.UiThread
-    private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi) {
+    private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
         if (mSaveUi == null) {
             // Calling destroySaveUiUiThread() twice is normal - it usually happens when the
             // first call is made after the SaveUI is hidden and the second when the session is
@@ -387,7 +391,7 @@
         if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): " + pendingSaveUi);
         mSaveUi.destroy();
         mSaveUi = null;
-        if (pendingSaveUi != null) {
+        if (pendingSaveUi != null && notifyClient) {
             try {
                 if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): notifying client");
                 pendingSaveUi.client.setSaveUiState(pendingSaveUi.id, false);
@@ -399,9 +403,9 @@
 
     @android.annotation.UiThread
     private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
-            @Nullable AutoFillUiCallback callback) {
+            @Nullable AutoFillUiCallback callback, boolean notifyClient) {
         hideFillUiUiThread(callback);
-        destroySaveUiUiThread(pendingSaveUi);
+        destroySaveUiUiThread(pendingSaveUi, notifyClient);
     }
 
     @android.annotation.UiThread
@@ -413,7 +417,7 @@
                 Slog.d(TAG, "hideAllUiThread(): "
                         + "destroying Save UI because pending restoration is finished");
             }
-            destroySaveUiUiThread(pendingSaveUi);
+            destroySaveUiUiThread(pendingSaveUi, true);
         }
     }
 }
diff --git a/com/android/server/autofill/ui/FillUi.java b/com/android/server/autofill/ui/FillUi.java
index 371e74d..bf442dc 100644
--- a/com/android/server/autofill/ui/FillUi.java
+++ b/com/android/server/autofill/ui/FillUi.java
@@ -55,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
 
 final class FillUi {
     private static final String TAG = "FillUi";
@@ -164,15 +165,18 @@
                         Slog.e(TAG, "Error inflating remote views", e);
                         continue;
                     }
-                    final AutofillValue value = dataset.getFieldValues().get(index);
+                    final Pattern filter = dataset.getFilter(index);
                     String valueText = null;
-                    // If the dataset needs auth - don't add its text to allow guessing
-                    // its content based on how filtering behaves.
-                    if (value != null && value.isText() && dataset.getAuthentication() == null) {
-                        valueText = value.getTextValue().toString().toLowerCase();
+                    if (filter == null) {
+                        final AutofillValue value = dataset.getFieldValues().get(index);
+                        // If the dataset needs auth - don't add its text to allow guessing
+                        // its content based on how filtering behaves.
+                        if (value != null && value.isText() && dataset.getAuthentication() == null) {
+                            valueText = value.getTextValue().toString().toLowerCase();
+                        }
                     }
 
-                    items.add(new ViewItem(dataset, valueText, view));
+                    items.add(new ViewItem(dataset, filter, valueText, view));
                 }
             }
 
@@ -331,11 +335,17 @@
         private final String mValue;
         private final Dataset mDataset;
         private final View mView;
+        private final Pattern mFilter;
 
-        ViewItem(Dataset dataset, String value, View view) {
+        ViewItem(Dataset dataset, Pattern filter, String value, View view) {
             mDataset = dataset;
             mValue = value;
             mView = view;
+            mFilter = filter;
+        }
+
+        public Pattern getFilter() {
+            return mFilter;
         }
 
         public View getView() {
@@ -349,12 +359,6 @@
         public String getValue() {
             return mValue;
         }
-
-        @Override
-        public String toString() {
-            // Used for filtering in the adapter
-            return mValue;
-        }
     }
 
     private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub {
@@ -516,10 +520,16 @@
                     for (int i = 0; i < itemCount; i++) {
                         final ViewItem item = mAllItems.get(i);
                         final String value = item.getValue();
-                        // No value, i.e. null, matches any filter
-                        if ((value == null && item.mDataset.getAuthentication() == null)
-                                || (value != null
-                                        && value.toLowerCase().startsWith(constraintLowerCase))) {
+                        final Pattern filter = item.getFilter();
+                        final boolean matches;
+                        if (filter != null) {
+                            matches = filter.matcher(constraintLowerCase).matches();
+                        } else {
+                            matches = (value == null)
+                                    ? (item.mDataset.getAuthentication() == null)
+                                    : value.toLowerCase().startsWith(constraintLowerCase);
+                        }
+                        if (matches) {
                             filteredItems.add(item);
                         }
                     }
diff --git a/com/android/server/autofill/ui/SaveUi.java b/com/android/server/autofill/ui/SaveUi.java
index d0b2e92..d48f23c 100644
--- a/com/android/server/autofill/ui/SaveUi.java
+++ b/com/android/server/autofill/ui/SaveUi.java
@@ -20,16 +20,15 @@
 import static com.android.server.autofill.Helper.sVerbose;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -53,6 +52,8 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
@@ -111,6 +112,7 @@
     }
 
     private final Handler mHandler = UiThread.getHandler();
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private final @NonNull Dialog mDialog;
 
@@ -121,16 +123,21 @@
     private final CharSequence mTitle;
     private final CharSequence mSubTitle;
     private final PendingUi mPendingUi;
+    private final String mServicePackageName;
+    private final String mPackageName;
 
     private boolean mDestroyed;
 
     SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi,
            @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
+           @Nullable String servicePackageName, @NonNull String packageName,
            @NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
            @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) {
         mPendingUi= pendingUi;
         mListener = new OneTimeListener(listener);
         mOverlayControl = overlayControl;
+        mServicePackageName = servicePackageName;
+        mPackageName = packageName;
 
         final LayoutInflater inflater = LayoutInflater.from(context);
         final View view = inflater.inflate(R.layout.autofill_save, null);
@@ -181,6 +188,8 @@
         ScrollView subtitleContainer = null;
         final CustomDescription customDescription = info.getCustomDescription();
         if (customDescription != null) {
+            writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
+
             mSubTitle = null;
             if (sDebug) Slog.d(TAG, "Using custom description");
 
@@ -190,40 +199,35 @@
                     @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.
-                        if (pendingIntent != null) {
-                            if (intent == null) {
-                                Slog.w(TAG,
-                                        "remote view on custom description does not have intent");
-                                return false;
-                            }
-                            if (!pendingIntent.isActivity()) {
-                                Slog.w(TAG, "ignoring custom description pending intent that's not "
-                                        + "for an activity: " + pendingIntent);
-                                return false;
-                            }
-                            if (sVerbose) {
-                                Slog.v(TAG,
-                                        "Intercepting custom description intent: " + 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();
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "error triggering pending intent: " + intent);
-                                return false;
-                            }
+                        final boolean isValid = isValidLink(pendingIntent, intent);
+                        if (!isValid) {
+                            log.setType(MetricsEvent.TYPE_UNKNOWN);
+                            mMetricsLogger.write(log);
+                            return false;
                         }
-                        return true;
+                        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;
+                        }
                     }
                 };
 
@@ -241,6 +245,7 @@
         } else {
             mSubTitle = info.getDescription();
             if (mSubTitle != null) {
+                writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_SUBTITLE, type);
                 subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
                 final TextView subtitleView = new TextView(context);
                 subtitleView.setText(mSubTitle);
@@ -258,9 +263,7 @@
         } else {
             noButton.setText(R.string.autofill_save_no);
         }
-        final View.OnClickListener cancelListener =
-                (v) -> mListener.onCancel(info.getNegativeActionListener());
-        noButton.setOnClickListener(cancelListener);
+        noButton.setOnClickListener((v) -> mListener.onCancel(info.getNegativeActionListener()));
 
         final View yesButton = view.findViewById(R.id.autofill_save_yes);
         yesButton.setOnClickListener((v) -> mListener.onSave());
@@ -268,8 +271,9 @@
         mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel);
         mDialog.setContentView(view);
 
-        // Dialog can be dismissed when touched outside.
-        mDialog.setOnDismissListener((d) -> mListener.onCancel(info.getNegativeActionListener()));
+        // Dialog can be dismissed when touched outside, but the negative listener should not be
+        // notified (hence the null argument).
+        mDialog.setOnDismissListener((d) -> mListener.onCancel(null));
 
         final Window window = mDialog.getWindow();
         window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
@@ -300,7 +304,7 @@
 
         if (actualWidth <= maxWidth && actualHeight <= maxHeight) {
             if (sDebug) {
-                Slog.d(TAG, "Addingservice icon "
+                Slog.d(TAG, "Adding service icon "
                         + "(" + actualWidth + "x" + actualHeight + ") as it's less than maximum "
                         + "(" + maxWidth + "x" + maxHeight + ").");
             }
@@ -309,10 +313,41 @@
             Slog.w(TAG, "Not adding service icon of size "
                     + "(" + actualWidth + "x" + actualHeight + ") because maximum is "
                     + "(" + maxWidth + "x" + maxHeight + ").");
-            iconView.setVisibility(View.INVISIBLE);
+            ((ViewGroup)iconView.getParent()).removeView(iconView);
         }
     }
 
+    private static boolean isValidLink(PendingIntent pendingIntent, Intent intent) {
+        if (pendingIntent == null) {
+            Slog.w(TAG, "isValidLink(): custom description without pending intent");
+            return false;
+        }
+        if (!pendingIntent.isActivity()) {
+            Slog.w(TAG, "isValidLink(): pending intent not for activity");
+            return false;
+        }
+        if (intent == null) {
+            Slog.w(TAG, "isValidLink(): no intent");
+            return false;
+        }
+        return true;
+    }
+
+    private LogMaker newLogMaker(int category, int saveType) {
+        return newLogMaker(category)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SAVE_TYPE, saveType);
+    }
+
+    private LogMaker newLogMaker(int category) {
+        return new LogMaker(category)
+                .setPackageName(mPackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, mServicePackageName);
+    }
+
+    private void writeLog(int category, int saveType) {
+        mMetricsLogger.write(newLogMaker(category, saveType));
+    }
+
     /**
      * Update the pending UI, if any.
      *
@@ -326,17 +361,25 @@
                     + mPendingUi.getToken());
             return;
         }
-        switch (operation) {
-            case AutofillManager.PENDING_UI_OPERATION_RESTORE:
-                if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
-                show();
-                break;
-            case AutofillManager.PENDING_UI_OPERATION_CANCEL:
-                if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
-                hide();
-                break;
-            default:
-                Slog.w(TAG, "restore(): invalid operation " + operation);
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_PENDING_SAVE_UI_OPERATION);
+        try {
+            switch (operation) {
+                case AutofillManager.PENDING_UI_OPERATION_RESTORE:
+                    if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
+                    log.setType(MetricsEvent.TYPE_OPEN);
+                    show();
+                    break;
+                case AutofillManager.PENDING_UI_OPERATION_CANCEL:
+                    log.setType(MetricsEvent.TYPE_DISMISS);
+                    if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
+                    hide();
+                    break;
+                default:
+                    log.setType(MetricsEvent.TYPE_FAILURE);
+                    Slog.w(TAG, "restore(): invalid operation " + operation);
+            }
+        } finally {
+            mMetricsLogger.write(log);
         }
         mPendingUi.setState(PendingUi.STATE_FINISHED);
     }
@@ -385,6 +428,8 @@
         pw.print(prefix); pw.print("title: "); pw.println(mTitle);
         pw.print(prefix); pw.print("subtitle: "); pw.println(mSubTitle);
         pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi);
+        pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
+        pw.print(prefix); pw.print("app: "); pw.println(mPackageName);
 
         final View view = mDialog.getWindow().getDecorView();
         final int[] loc = view.getLocationOnScreen();
diff --git a/com/android/server/backup/BackupManagerConstants.java b/com/android/server/backup/BackupManagerConstants.java
index cd60182..245241c 100644
--- a/com/android/server/backup/BackupManagerConstants.java
+++ b/com/android/server/backup/BackupManagerConstants.java
@@ -123,7 +123,7 @@
     // group the calls of these methods in a block syncrhonized on
     // a reference of this object.
     public synchronized long getKeyValueBackupIntervalMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupIntervalMilliseconds(...) returns "
                     + mKeyValueBackupIntervalMilliseconds);
         }
@@ -131,7 +131,7 @@
     }
 
     public synchronized long getKeyValueBackupFuzzMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupFuzzMilliseconds(...) returns "
                     + mKeyValueBackupFuzzMilliseconds);
         }
@@ -139,7 +139,7 @@
     }
 
     public synchronized boolean getKeyValueBackupRequireCharging() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupRequireCharging(...) returns "
                     + mKeyValueBackupRequireCharging);
         }
@@ -147,7 +147,7 @@
     }
 
     public synchronized int getKeyValueBackupRequiredNetworkType() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupRequiredNetworkType(...) returns "
                     + mKeyValueBackupRequiredNetworkType);
         }
@@ -155,7 +155,7 @@
     }
 
     public synchronized long getFullBackupIntervalMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupIntervalMilliseconds(...) returns "
                     + mFullBackupIntervalMilliseconds);
         }
@@ -163,7 +163,7 @@
     }
 
     public synchronized boolean getFullBackupRequireCharging() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging);
         }
         return mFullBackupRequireCharging;
@@ -171,7 +171,7 @@
     }
 
     public synchronized int getFullBackupRequiredNetworkType() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupRequiredNetworkType(...) returns "
                     + mFullBackupRequiredNetworkType);
         }
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index f525797..eabe21f 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -192,6 +192,10 @@
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 
+/**
+ * @Deprecated Use RefactoredBackupManagerService instead. This class is only
+ * kept for fallback and archeology reasons and will be removed soon.
+ */
 public class BackupManagerService implements BackupManagerServiceInterface {
 
     private static final String TAG = "BackupManagerService";
@@ -397,45 +401,53 @@
         @Override
         public void onUnlockUser(int userId) {
             if (userId == UserHandle.USER_SYSTEM) {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
-                sInstance.initialize(userId);
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                // Migrate legacy setting
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
-                if (!backupSettingMigrated(userId)) {
-                    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, userId);
-                    if (enableState >= 0) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                        }
-                        writeBackupEnableState(enableState != 0, userId);
-                        Settings.Secure.putStringForUser(r,
-                                Settings.Secure.BACKUP_ENABLED, null, userId);
-                    } else {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                        }
-                    }
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
-                try {
-                    sInstance.setBackupEnabled(readBackupEnableState(userId));
-                } catch (RemoteException e) {
-                    // can't happen; it's a local object
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                sInstance.unlockSystemUser();
             }
         }
     }
 
+    // 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)) {
+                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, "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");
+                    }
+                }
+            }
+            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 {
         public ProvisionedObserver(Handler handler) {
             super(handler);
@@ -1983,7 +1995,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[] { pkgName };
                 }
@@ -1991,7 +2003,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -2001,7 +2013,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -2033,7 +2046,7 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (appGetsFullBackup(app)
@@ -2050,7 +2063,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -2074,8 +2088,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
diff --git a/com/android/server/backup/BackupManagerServiceInterface.java b/com/android/server/backup/BackupManagerServiceInterface.java
index 5dfa630..041f9ed 100644
--- a/com/android/server/backup/BackupManagerServiceInterface.java
+++ b/com/android/server/backup/BackupManagerServiceInterface.java
@@ -39,6 +39,8 @@
  */
 public interface BackupManagerServiceInterface {
 
+  void unlockSystemUser();
+
   // Utility: build a new random integer token
   int generateRandomIntegerToken();
 
diff --git a/com/android/server/backup/FullBackupJob.java b/com/android/server/backup/FullBackupJob.java
index 82638b4..b81a54d 100644
--- a/com/android/server/backup/FullBackupJob.java
+++ b/com/android/server/backup/FullBackupJob.java
@@ -61,7 +61,7 @@
     @Override
     public boolean onStartJob(JobParameters params) {
         mParams = params;
-        Trampoline service = BackupManagerService.getInstance();
+        Trampoline service = RefactoredBackupManagerService.getInstance();
         return service.beginFullBackup(this);
     }
 
@@ -69,7 +69,7 @@
     public boolean onStopJob(JobParameters params) {
         if (mParams != null) {
             mParams = null;
-            Trampoline service = BackupManagerService.getInstance();
+            Trampoline service = RefactoredBackupManagerService.getInstance();
             service.endFullBackup();
         }
         return false;
diff --git a/com/android/server/backup/KeyValueAdbBackupEngine.java b/com/android/server/backup/KeyValueAdbBackupEngine.java
index 279c828..b38b25a 100644
--- a/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -4,8 +4,8 @@
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
-import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
-import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_BACKUP_INTERVAL;
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
@@ -19,6 +19,8 @@
 import android.os.SELinux;
 import android.util.Slog;
 
+import com.android.server.backup.utils.FullBackupUtils;
+
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -78,7 +80,7 @@
         mNewStateName = new File(mStateDir,
                 pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
 
-        mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+        mManifestFile = new File(mDataDir, RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME);
     }
 
     public void backupOnePackage() throws IOException {
@@ -188,7 +190,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
                 }
-                BackupManagerService.writeAppManifest(
+                FullBackupUtils.writeAppManifest(
                         mPackage, mPackageManager, mManifestFile, false, false);
                 FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
                         mDataDir.getAbsolutePath(),
@@ -251,7 +253,7 @@
             t.start();
 
             // Now pull data from the app and stuff it into the output
-            BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+            FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
 
             if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                 Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
diff --git a/com/android/server/backup/KeyValueAdbRestoreEngine.java b/com/android/server/backup/KeyValueAdbRestoreEngine.java
index b62bb5c..a2de8e7 100644
--- a/com/android/server/backup/KeyValueAdbRestoreEngine.java
+++ b/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -13,6 +13,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.backup.restore.PerformAdbRestoreTask;
+
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -41,7 +43,7 @@
     private final File mDataDir;
 
     FileMetadata mInfo;
-    BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+    PerformAdbRestoreTask mRestoreTask;
     ParcelFileDescriptor mInFD;
     IBackupAgent mAgent;
     int mToken;
diff --git a/com/android/server/backup/KeyValueBackupJob.java b/com/android/server/backup/KeyValueBackupJob.java
index d8411e2..5dfb0bc 100644
--- a/com/android/server/backup/KeyValueBackupJob.java
+++ b/com/android/server/backup/KeyValueBackupJob.java
@@ -71,7 +71,7 @@
             if (delay <= 0) {
                 delay = interval + new Random().nextInt((int) fuzz);
             }
-            if (BackupManagerService.DEBUG_SCHEDULING) {
+            if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
                 Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
             }
             JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
@@ -110,7 +110,7 @@
         }
 
         // Time to run a key/value backup!
-        Trampoline service = BackupManagerService.getInstance();
+        Trampoline service = RefactoredBackupManagerService.getInstance();
         try {
             service.backupNow();
         } catch (RemoteException e) {}
diff --git a/com/android/server/backup/PackageManagerBackupAgent.java b/com/android/server/backup/PackageManagerBackupAgent.java
index 8d91e0d..f658f22 100644
--- a/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/com/android/server/backup/PackageManagerBackupAgent.java
@@ -30,6 +30,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
+import com.android.server.backup.utils.AppBackupUtils;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -140,7 +142,7 @@
         int N = pkgs.size();
         for (int a = N-1; a >= 0; a--) {
             PackageInfo pkg = pkgs.get(a);
-            if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
                 pkgs.remove(a);
             }
         }
diff --git a/com/android/server/backup/ProcessedPackagesJournal.java b/com/android/server/backup/ProcessedPackagesJournal.java
index 187d5d9..e29b7d5 100644
--- a/com/android/server/backup/ProcessedPackagesJournal.java
+++ b/com/android/server/backup/ProcessedPackagesJournal.java
@@ -21,7 +21,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.backup.RefactoredBackupManagerService;
 
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
 import java.io.EOFException;
+import java.io.FileInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -130,7 +133,8 @@
             return;
         }
 
-        try (RandomAccessFile oldJournal = new RandomAccessFile(journalFile, "r")) {
+        try (DataInputStream oldJournal = new DataInputStream(
+                new BufferedInputStream(new FileInputStream(journalFile)))) {
             while (true) {
                 String packageName = oldJournal.readUTF();
                 if (DEBUG) {
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index 141f920..f298065 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -77,6 +77,7 @@
 import android.os.SELinux;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
@@ -149,6 +150,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class RefactoredBackupManagerService implements BackupManagerServiceInterface {
 
@@ -546,39 +548,53 @@
         @Override
         public void onUnlockUser(int userId) {
             if (userId == UserHandle.USER_SYSTEM) {
-                sInstance.initialize(userId);
-
-                // Migrate legacy setting
-                if (!backupSettingMigrated(userId)) {
-                    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, userId);
-                    if (enableState >= 0) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                        }
-                        writeBackupEnableState(enableState != 0, userId);
-                        Settings.Secure.putStringForUser(r,
-                                Settings.Secure.BACKUP_ENABLED, null, userId);
-                    } else {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                        }
-                    }
-                }
-
-                try {
-                    sInstance.setBackupEnabled(readBackupEnableState(userId));
-                } catch (RemoteException e) {
-                    // can't happen; it's a local object
-                }
+                sInstance.unlockSystemUser();
             }
         }
     }
 
+    // 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)) {
+                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, "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");
+                    }
+                }
+            }
+            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
     // token is the index of the entry in the pending-operations list.
     public static final int OP_PENDING = 0;
@@ -619,6 +635,7 @@
     private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
     private final Object mCurrentOpLock = new Object();
     private final Random mTokenGenerator = new Random();
+    final AtomicInteger mNextToken = new AtomicInteger();
 
     private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
 
@@ -644,7 +661,7 @@
 
     // Persistently track the need to do a full init
     private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
-    private ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
+    private final ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
 
     // Round-robin queue for scheduling full backup passes
     private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
@@ -658,18 +675,41 @@
     @GuardedBy("mQueueLock")
     private ArrayList<FullBackupEntry> mFullBackupQueue;
 
-    // Utility: build a new random integer token
+    // Utility: build a new random integer token. The low bits are the ordinal of the
+    // operation for near-time uniqueness, and the upper bits are random for app-
+    // side unpredictability.
     @Override
     public int generateRandomIntegerToken() {
-        int token;
-        do {
-            synchronized (mTokenGenerator) {
-                token = mTokenGenerator.nextInt();
-            }
-        } while (token < 0);
+        int token = mTokenGenerator.nextInt();
+        if (token < 0) token = -token;
+        token &= ~0xFF;
+        token |= (mNextToken.incrementAndGet() & 0xFF);
         return token;
     }
 
+    /*
+     * Construct a backup agent instance for the metadata pseudopackage.  This is a
+     * process-local non-lifecycle agent instance, so we manually set up the context
+     * topology for it.
+     */
+    public PackageManagerBackupAgent makeMetadataAgent() {
+        PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager);
+        pmAgent.attach(mContext);
+        pmAgent.onCreate();
+        return pmAgent;
+    }
+
+    /*
+     * Same as above but with the explicit package-set configuration.
+     */
+    public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) {
+        PackageManagerBackupAgent pmAgent =
+                new PackageManagerBackupAgent(mPackageManager, packages);
+        pmAgent.attach(mContext);
+        pmAgent.onCreate();
+        return pmAgent;
+    }
+
     // ----- Debug-only backup operation trace -----
     public void addBackupTrace(String s) {
         if (DEBUG_BACKUP_TRACE) {
@@ -800,17 +840,18 @@
 
         // Remember our ancestral dataset
         mTokenFile = new File(mBaseStateDir, "ancestral");
-        try (RandomAccessFile tf = new RandomAccessFile(mTokenFile, "r")) {
-            int version = tf.readInt();
+        try (DataInputStream tokenStream = new DataInputStream(new BufferedInputStream(
+                new FileInputStream(mTokenFile)))) {
+            int version = tokenStream.readInt();
             if (version == CURRENT_ANCESTRAL_RECORD_VERSION) {
-                mAncestralToken = tf.readLong();
-                mCurrentToken = tf.readLong();
+                mAncestralToken = tokenStream.readLong();
+                mCurrentToken = tokenStream.readLong();
 
-                int numPackages = tf.readInt();
+                int numPackages = tokenStream.readInt();
                 if (numPackages >= 0) {
                     mAncestralPackages = new HashSet<>();
                     for (int i = 0; i < numPackages; i++) {
-                        String pkgName = tf.readUTF();
+                        String pkgName = tokenStream.readUTF();
                         mAncestralPackages.add(pkgName);
                     }
                 }
@@ -878,7 +919,7 @@
                         PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
                         if (AppBackupUtils.appGetsFullBackup(pkg)
                                 && AppBackupUtils.appIsEligibleForBackup(
-                                pkg.applicationInfo)) {
+                                pkg.applicationInfo, mPackageManager)) {
                             schedule.add(new FullBackupEntry(pkgName, lastBackup));
                         } else {
                             if (DEBUG) {
@@ -899,7 +940,7 @@
                 for (PackageInfo app : apps) {
                     if (AppBackupUtils.appGetsFullBackup(app)
                             && AppBackupUtils.appIsEligibleForBackup(
-                            app.applicationInfo)) {
+                            app.applicationInfo, mPackageManager)) {
                         if (!foundApps.contains(app.packageName)) {
                             if (MORE_DEBUG) {
                                 Slog.i(TAG, "New full backup app " + app.packageName + " found");
@@ -925,7 +966,7 @@
             schedule = new ArrayList<>(apps.size());
             for (PackageInfo info : apps) {
                 if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
-                        info.applicationInfo)) {
+                        info.applicationInfo, mPackageManager)) {
                     schedule.add(new FullBackupEntry(info.packageName, 0));
                 }
             }
@@ -1155,7 +1196,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[]{pkgName};
                 }
@@ -1163,7 +1204,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -1173,7 +1214,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -1205,12 +1247,12 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (AppBackupUtils.appGetsFullBackup(app)
                                 && AppBackupUtils.appIsEligibleForBackup(
-                                app.applicationInfo)) {
+                                app.applicationInfo, mPackageManager)) {
                             enqueueFullBackup(packageName, now);
                             scheduleNextFullBackupJob(0);
                         } else {
@@ -1223,7 +1265,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -1247,8 +1290,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
@@ -1507,7 +1551,7 @@
 
         long token = mAncestralToken;
         synchronized (mQueueLock) {
-            if (mProcessedPackagesJournal.hasBeenProcessed(packageName)) {
+            if (mCurrentToken != 0 && mProcessedPackagesJournal.hasBeenProcessed(packageName)) {
                 if (MORE_DEBUG) {
                     Slog.i(TAG, "App in ever-stored, so using current token");
                 }
@@ -1568,7 +1612,8 @@
             try {
                 PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
                         PackageManager.GET_SIGNATURES);
-                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
+                        mPackageManager)) {
                     BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
                             BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                     continue;
@@ -1759,8 +1804,12 @@
                 // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
                 // called after we receive cancel here. We need this op's state there.
 
-                // Remove all pending timeout messages for this operation type.
-                mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+                // Remove all pending timeout messages of types OP_TYPE_BACKUP_WAIT and
+                // OP_TYPE_RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
+                // doesn't require cancellation.
+                if (op.type == OP_TYPE_BACKUP_WAIT || op.type == OP_TYPE_RESTORE_WAIT) {
+                    mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+                }
             }
             mCurrentOpLock.notifyAll();
         }
@@ -2108,14 +2157,26 @@
     // so tear down any ongoing backup task right away.
     @Override
     public void endFullBackup() {
-        synchronized (mQueueLock) {
-            if (mRunningFullBackupTask != null) {
-                if (DEBUG_SCHEDULING) {
-                    Slog.i(TAG, "Telling running backup to stop");
+        // offload the mRunningFullBackupTask.handleCancel() call to another thread,
+        // as we might have to wait for mCancelLock
+        Runnable endFullBackupRunnable = new Runnable() {
+            @Override
+            public void run() {
+                PerformFullTransportBackupTask pftbt = null;
+                synchronized (mQueueLock) {
+                    if (mRunningFullBackupTask != null) {
+                        pftbt = mRunningFullBackupTask;
+                    }
                 }
-                mRunningFullBackupTask.handleCancel(true);
+                if (pftbt != null) {
+                    if (DEBUG_SCHEDULING) {
+                        Slog.i(TAG, "Telling running backup to stop");
+                    }
+                    pftbt.handleCancel(true);
+                }
             }
-        }
+        };
+        new Thread(endFullBackupRunnable, "end-full-backup").start();
     }
 
     // Used by both incremental and full restore
@@ -2800,8 +2861,7 @@
         final long oldId = Binder.clearCallingIdentity();
         try {
             String prevTransport = mTransportManager.selectTransport(transport);
-            Settings.Secure.putString(mContext.getContentResolver(),
-                    Settings.Secure.BACKUP_TRANSPORT, transport);
+            updateStateForTransport(transport);
             Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
                     + " returning " + prevTransport);
             return prevTransport;
@@ -2826,9 +2886,7 @@
                     @Override
                     public void onSuccess(String transportName) {
                         mTransportManager.selectTransport(transportName);
-                        Settings.Secure.putString(mContext.getContentResolver(),
-                                Settings.Secure.BACKUP_TRANSPORT,
-                                mTransportManager.getCurrentTransportName());
+                        updateStateForTransport(mTransportManager.getCurrentTransportName());
                         Slog.v(TAG, "Transport successfully selected: "
                                 + transport.flattenToShortString());
                         try {
@@ -2853,6 +2911,28 @@
         Binder.restoreCallingIdentity(oldId);
     }
 
+    private void updateStateForTransport(String newTransportName) {
+        // Publish the name change
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.BACKUP_TRANSPORT, newTransportName);
+
+        // And update our current-dataset bookkeeping
+        IBackupTransport transport = mTransportManager.getTransportBinder(newTransportName);
+        if (transport != null) {
+            try {
+                mCurrentToken = transport.getCurrentRestoreSet();
+            } catch (Exception e) {
+                // Oops.  We can't know the current dataset token, so reset and figure it out
+                // when we do the next k/v backup operation on this transport.
+                mCurrentToken = 0;
+            }
+        } else {
+            // The named transport isn't bound at this particular moment, so we can't
+            // know yet what its current dataset token is.  Reset as above.
+            mCurrentToken = 0;
+        }
+    }
+
     // Supply the configuration Intent for the given transport.  If the name is not one
     // of the available transports, or if the transport does not supply any configuration
     // UI, the method returns null.
@@ -3162,19 +3242,6 @@
         }
     }
 
-    // We also avoid backups of 'disabled' apps
-    private static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
-        switch (pm.getApplicationEnabledSetting(app.packageName)) {
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
-                return true;
-
-            default:
-                return false;
-        }
-    }
-
     @Override
     public boolean isAppEligibleForBackup(String packageName) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -3182,9 +3249,10 @@
         try {
             PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
                     PackageManager.GET_SIGNATURES);
-            if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo) ||
+            if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
+                            mPackageManager) ||
                     AppBackupUtils.appIsStopped(packageInfo.applicationInfo) ||
-                    appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
+                    AppBackupUtils.appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
                 return false;
             }
             IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
diff --git a/com/android/server/backup/Trampoline.java b/com/android/server/backup/Trampoline.java
index fcd929a..9739e38 100644
--- a/com/android/server/backup/Trampoline.java
+++ b/com/android/server/backup/Trampoline.java
@@ -98,7 +98,7 @@
 
     protected boolean isRefactoredServiceEnabled() {
         return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 1) == 0;
+                Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 0) == 0;
     }
 
     protected int binderGetCallingUid() {
@@ -139,6 +139,13 @@
         }
     }
 
+    void unlockSystemUser() {
+        BackupManagerServiceInterface svc = mService;
+        if (svc != null) {
+            svc.unlockSystemUser();
+        }
+    }
+
     public void setBackupServiceActive(final int userHandle, boolean makeActive) {
         // Only the DPM should be changing the active state of backup
         final int caller = binderGetCallingUid();
diff --git a/com/android/server/backup/TransportManager.java b/com/android/server/backup/TransportManager.java
index 9aae384..7a0173f 100644
--- a/com/android/server/backup/TransportManager.java
+++ b/com/android/server/backup/TransportManager.java
@@ -341,9 +341,9 @@
     private class TransportConnection implements ServiceConnection {
 
         // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
-        private IBackupTransport mBinder;
+        private volatile IBackupTransport mBinder;
         private final List<TransportReadyCallback> mListeners = new ArrayList<>();
-        private String mTransportName;
+        private volatile String mTransportName;
 
         private final ComponentName mTransportComponent;
 
@@ -426,25 +426,24 @@
                     + rebindTimeout + "ms");
         }
 
+        // Intentionally not synchronized -- the variable is volatile and changes to its value
+        // are inside synchronized blocks, providing a memory sync barrier; and this method
+        // does not touch any other state protected by that lock.
         private IBackupTransport getBinder() {
-            synchronized (mTransportLock) {
-                return mBinder;
-            }
+            return mBinder;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private String getName() {
-            synchronized (mTransportLock) {
-                return mTransportName;
-            }
+            return mTransportName;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private void bindIfUnbound() {
-            synchronized (mTransportLock) {
-                if (mBinder == null) {
-                    Slog.d(TAG,
-                            "Rebinding to transport " + mTransportComponent.flattenToShortString());
-                    bindToTransport(mTransportComponent, this);
-                }
+            if (mBinder == null) {
+                Slog.d(TAG,
+                        "Rebinding to transport " + mTransportComponent.flattenToShortString());
+                bindToTransport(mTransportComponent, this);
             }
         }
 
diff --git a/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 4085f63..f0b3e4a 100644
--- a/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -236,12 +236,11 @@
         obbConnection.establish();  // we'll want this later
 
         sendStartBackup();
+        PackageManager pm = backupManagerService.getPackageManager();
 
         // doAllApps supersedes the package set if any
         if (mAllApps) {
-            List<PackageInfo> allPackages =
-                    backupManagerService.getPackageManager().getInstalledPackages(
-                            PackageManager.GET_SIGNATURES);
+            List<PackageInfo> allPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
             for (int i = 0; i < allPackages.size(); i++) {
                 PackageInfo pkg = allPackages.get(i);
                 // Exclude system apps if we've been asked to do so
@@ -288,7 +287,7 @@
         Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
         while (iter.hasNext()) {
             PackageInfo pkg = iter.next().getValue();
-            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo)
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
                     || AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
                 iter.remove();
                 if (DEBUG) {
diff --git a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index bc7c117..90134e1 100644
--- a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -140,10 +140,10 @@
 
         for (String pkg : whichPackages) {
             try {
-                PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(pkg,
-                        PackageManager.GET_SIGNATURES);
+                PackageManager pm = backupManagerService.getPackageManager();
+                PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
                 mCurrentPackage = info;
-                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
                     // Cull any packages that have indicated that backups are not permitted,
                     // that run as system-domain uids but do not define their own backup agents,
                     // as well as any explicit mention of the 'special' shared-storage agent
@@ -306,6 +306,7 @@
             final int N = mPackages.size();
             final byte[] buffer = new byte[8192];
             for (int i = 0; i < N; i++) {
+                mBackupRunner = null;
                 PackageInfo currentPackage = mPackages.get(i);
                 String packageName = currentPackage.packageName;
                 if (DEBUG) {
@@ -491,7 +492,13 @@
                     }
                     EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE, packageName,
                             "transport rejected");
-                    // Do nothing, clean up, and continue looping.
+                    // This failure state can come either a-priori from the transport, or
+                    // from the preflight pass.  If we got as far as preflight, we now need
+                    // to tear down the target process.
+                    if (mBackupRunner != null) {
+                        backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+                    }
+                    // ... and continue looping.
                 } else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
                     BackupObserverUtils
                             .sendBackupOnPackageResult(mBackupObserver, packageName,
@@ -501,6 +508,7 @@
                         EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
                                 packageName);
                     }
+                    backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
                     // Do nothing, clean up, and continue looping.
                 } else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
                     BackupObserverUtils
@@ -527,6 +535,7 @@
                     EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
                     // Abort entire backup pass.
                     backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+                    backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
                     return;
                 } else {
                     // Success!
diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java
index ce4f906..7a8a920 100644
--- a/com/android/server/backup/internal/PerformBackupTask.java
+++ b/com/android/server/backup/internal/PerformBackupTask.java
@@ -227,9 +227,8 @@
                     if (!mFinished) {
                         finalizeBackup();
                     } else {
-                        Slog.e(TAG, "Duplicate finish");
+                        Slog.e(TAG, "Duplicate finish of K/V pass");
                     }
-                    mFinished = true;
                     break;
             }
         }
@@ -322,8 +321,7 @@
                 // because it's cheap and this way we guarantee that we don't get out of
                 // step even if we're selecting among various transports at run time.
                 if (mStatus == BackupTransport.TRANSPORT_OK) {
-                    PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
-                            backupManagerService.getPackageManager());
+                    PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
                     mStatus = invokeAgentForBackup(
                             PACKAGE_MANAGER_SENTINEL,
                             IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
@@ -391,11 +389,9 @@
         // to sanity-check here.  This also gives us the classname of the
         // package's backup agent.
         try {
-            mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo(
-                    request.packageName,
-                    PackageManager.GET_SIGNATURES);
-            if (!AppBackupUtils.appIsEligibleForBackup(
-                    mCurrentPackage.applicationInfo)) {
+            PackageManager pm = backupManagerService.getPackageManager();
+            mCurrentPackage = pm.getPackageInfo(request.packageName, PackageManager.GET_SIGNATURES);
+            if (!AppBackupUtils.appIsEligibleForBackup(mCurrentPackage.applicationInfo, pm)) {
                 // The manifest has changed but we had a stale backup request pending.
                 // This won't happen again because the app won't be requesting further
                 // backups.
@@ -609,6 +605,7 @@
                     break;
             }
         }
+        mFinished = true;
         Slog.i(TAG, "K/V backup pass finished.");
         // Only once we're entirely finished do we release the wakelock for k/v backup.
         backupManagerService.getWakelock().release();
diff --git a/com/android/server/backup/internal/RunInitializeReceiver.java b/com/android/server/backup/internal/RunInitializeReceiver.java
index a6897d0..1df0bf0 100644
--- a/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -23,6 +23,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.server.backup.RefactoredBackupManagerService;
@@ -38,19 +39,22 @@
     public void onReceive(Context context, Intent intent) {
         if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
             synchronized (backupManagerService.getQueueLock()) {
+                final ArraySet<String> pendingInits = backupManagerService.getPendingInits();
                 if (DEBUG) {
-                    Slog.v(TAG, "Running a device init");
+                    Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
                 }
 
-                String[] pendingInits = (String[]) backupManagerService.getPendingInits().toArray();
-                backupManagerService.clearPendingInits();
-                PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
-                        pendingInits, null);
+                if (pendingInits.size() > 0) {
+                    final String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
+                    PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
+                            transports, null);
 
-                // Acquire the wakelock and pass it to the init thread.  it will
-                // be released once init concludes.
-                backupManagerService.getWakelock().acquire();
-                backupManagerService.getBackupHandler().post(initTask);
+                    // Acquire the wakelock and pass it to the init thread.  it will
+                    // be released once init concludes.
+                    backupManagerService.clearPendingInits();
+                    backupManagerService.getWakelock().acquire();
+                    backupManagerService.getBackupHandler().post(initTask);
+                }
             }
         }
     }
diff --git a/com/android/server/backup/restore/PerformAdbRestoreTask.java b/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 62ae065..22691bb 100644
--- a/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -150,8 +150,7 @@
         mObserver = observer;
         mLatchObject = latch;
         mAgent = null;
-        mPackageManagerBackupAgent = new PackageManagerBackupAgent(
-                backupManagerService.getPackageManager());
+        mPackageManagerBackupAgent = backupManagerService.makeMetadataAgent();
         mAgentPackage = null;
         mTargetApp = null;
         mObbConnection = new FullBackupObbConnection(backupManagerService);
diff --git a/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 21d5dc2..b538c6d 100644
--- a/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -198,8 +198,8 @@
             boolean hasSettings = false;
             for (int i = 0; i < filterSet.length; i++) {
                 try {
-                    PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
-                            filterSet[i], 0);
+                    PackageManager pm = backupManagerService.getPackageManager();
+                    PackageInfo info = pm.getPackageInfo(filterSet[i], 0);
                     if ("android".equals(info.packageName)) {
                         hasSystem = true;
                         continue;
@@ -209,8 +209,7 @@
                         continue;
                     }
 
-                    if (AppBackupUtils.appIsEligibleForBackup(
-                            info.applicationInfo)) {
+                    if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
                         mAcceptSet.add(info);
                     }
                 } catch (NameNotFoundException e) {
@@ -387,8 +386,7 @@
             // Pull the Package Manager metadata from the restore set first
             mCurrentPackage = new PackageInfo();
             mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
-            mPmAgent = new PackageManagerBackupAgent(backupManagerService.getPackageManager(),
-                    null);
+            mPmAgent = backupManagerService.makeMetadataAgent(null);
             mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
             if (MORE_DEBUG) {
                 Slog.v(TAG, "initiating restore for PMBA");
@@ -779,6 +777,9 @@
 
     // state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
     private void restoreFinished() {
+        if (DEBUG) {
+            Slog.d(TAG, "restoreFinished packageName=" + mCurrentPackage.packageName);
+        }
         try {
             backupManagerService
                     .prepareOperationTimeout(mEphemeralOpToken,
diff --git a/com/android/server/backup/utils/AppBackupUtils.java b/com/android/server/backup/utils/AppBackupUtils.java
index 4abf18a..d7cac77 100644
--- a/com/android/server/backup/utils/AppBackupUtils.java
+++ b/com/android/server/backup/utils/AppBackupUtils.java
@@ -22,6 +22,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.Signature;
 import android.os.Process;
 import android.util.Slog;
@@ -44,7 +45,7 @@
      *     <li>it is the special shared-storage backup package used for 'adb backup'
      * </ol>
      */
-    public static boolean appIsEligibleForBackup(ApplicationInfo app) {
+    public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
         // 1. their manifest states android:allowBackup="false"
         if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
             return false;
@@ -60,11 +61,33 @@
             return false;
         }
 
-        return true;
+        // 4. it is an "instant" app
+        if (app.isInstantApp()) {
+            return false;
+        }
+
+        // Everything else checks out; the only remaining roadblock would be if the
+        // package were disabled
+        return !appIsDisabled(app, pm);
+    }
+
+    /** Avoid backups of 'disabled' apps. */
+    public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
+        switch (pm.getApplicationEnabledSetting(app.packageName)) {
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                return true;
+
+            default:
+                return false;
+        }
     }
 
     /**
-     * Checks if the app is in a stopped state, that means it won't receive broadcasts.
+     * Checks if the app is in a stopped state.  This is not part of the general "eligible for
+     * backup?" check because we *do* still need to restore data to apps in this state (e.g.
+     * newly-installing ones)
      */
     public static boolean appIsStopped(ApplicationInfo app) {
         return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0);
diff --git a/com/android/server/connectivity/IpConnectivityEventBuilder.java b/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 22330e6..67e7216 100644
--- a/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -126,7 +126,7 @@
         wakeupStats.systemWakeups = in.systemWakeups;
         wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups;
         wakeupStats.applicationWakeups = in.applicationWakeups;
-        wakeupStats.unroutedWakeups = in.unroutedWakeups;
+        wakeupStats.noUidWakeups = in.noUidWakeups;
         final IpConnectivityEvent out = buildEvent(0, 0, in.iface);
         out.setWakeupStats(wakeupStats);
         return out;
diff --git a/com/android/server/connectivity/IpConnectivityMetrics.java b/com/android/server/connectivity/IpConnectivityMetrics.java
index 475d786..f2445fa 100644
--- a/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -34,6 +34,7 @@
 import android.util.Log;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
 import com.android.server.SystemService;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
@@ -44,7 +45,11 @@
 import java.util.List;
 import java.util.function.ToIntFunction;
 
-/** {@hide} */
+/**
+ * Event buffering service for core networking and connectivity metrics.
+ *
+ * {@hide}
+ */
 final public class IpConnectivityMetrics extends SystemService {
     private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
     private static final boolean DBG = false;
@@ -58,7 +63,10 @@
 
     private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
 
-    // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
+    // Default size of the event rolling log for bug report dumps.
+    private static final int DEFAULT_LOG_SIZE = 500;
+    // Default size of the event buffer for metrics reporting.
+    // Once the buffer is full, incoming events are dropped.
     private static final int DEFAULT_BUFFER_SIZE = 2000;
     // Maximum size of the event buffer.
     private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
@@ -67,24 +75,38 @@
 
     private static final int ERROR_RATE_LIMITED = -1;
 
-    // Lock ensuring that concurrent manipulations of the event buffer are correct.
+    // Lock ensuring that concurrent manipulations of the event buffers are correct.
     // There are three concurrent operations to synchronize:
     //  - appending events to the buffer.
     //  - iterating throught the buffer.
     //  - flushing the buffer content and replacing it by a new buffer.
     private final Object mLock = new Object();
 
+    // Implementation instance of IIpConnectivityMetrics.aidl.
     @VisibleForTesting
     public final Impl impl = new Impl();
+    // Subservice listening to Netd events via INetdEventListener.aidl.
     @VisibleForTesting
     NetdEventListenerService mNetdListener;
 
+    // Rolling log of the most recent events. This log is used for dumping
+    // connectivity events in bug reports.
+    @GuardedBy("mLock")
+    private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
+            new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
+    // Buffer of connectivity events used for metrics reporting. This buffer
+    // does not rotate automatically and instead saturates when it becomes full.
+    // It is flushed at metrics reporting.
     @GuardedBy("mLock")
     private ArrayList<ConnectivityMetricsEvent> mBuffer;
+    // Total number of events dropped from mBuffer since last metrics reporting.
     @GuardedBy("mLock")
     private int mDropped;
+    // Capacity of mBuffer
     @GuardedBy("mLock")
     private int mCapacity;
+    // A list of rate limiting counters keyed by connectivity event types for
+    // metrics reporting mBuffer.
     @GuardedBy("mLock")
     private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
 
@@ -132,6 +154,7 @@
     private int append(ConnectivityMetricsEvent event) {
         if (DBG) Log.d(TAG, "logEvent: " + event);
         synchronized (mLock) {
+            mEventLog.append(event);
             final int left = mCapacity - mBuffer.size();
             if (event == null) {
                 return left;
@@ -216,6 +239,23 @@
         }
     }
 
+    /**
+     * Prints for bug reports the content of the rolling event log and the
+     * content of Netd event listener.
+     */
+    private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final ConnectivityMetricsEvent[] events;
+        synchronized (mLock) {
+            events = mEventLog.toArray();
+        }
+        for (ConnectivityMetricsEvent ev : events) {
+            pw.println(ev.toString());
+        }
+        if (mNetdListener != null) {
+            mNetdListener.list(pw);
+        }
+    }
+
     private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mLock) {
             pw.println("Buffered events: " + mBuffer.size());
@@ -258,7 +298,8 @@
                     cmdFlush(fd, pw, args);
                     return;
                 case CMD_DUMPSYS:
-                    // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
+                    cmdDumpsys(fd, pw, args);
+                    return;
                 case CMD_LIST:
                     cmdList(fd, pw, args);
                     return;
diff --git a/com/android/server/connectivity/Nat464Xlat.java b/com/android/server/connectivity/Nat464Xlat.java
index e6585ad..fceacba 100644
--- a/com/android/server/connectivity/Nat464Xlat.java
+++ b/com/android/server/connectivity/Nat464Xlat.java
@@ -20,6 +20,7 @@
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkInfo;
 import android.net.RouteInfo;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
@@ -44,12 +45,18 @@
     // This must match the interface prefix in clatd.c.
     private static final String CLAT_PREFIX = "v4-";
 
-    // The network types we will start clatd on,
+    // The network types on which we will start clatd,
     // allowing clat only on networks for which we can support IPv6-only.
     private static final int[] NETWORK_TYPES = {
-            ConnectivityManager.TYPE_MOBILE,
-            ConnectivityManager.TYPE_WIFI,
-            ConnectivityManager.TYPE_ETHERNET,
+        ConnectivityManager.TYPE_MOBILE,
+        ConnectivityManager.TYPE_WIFI,
+        ConnectivityManager.TYPE_ETHERNET,
+    };
+
+    // The network states in which running clatd is supported.
+    private static final NetworkInfo.State[] NETWORK_STATES = {
+        NetworkInfo.State.CONNECTED,
+        NetworkInfo.State.SUSPENDED,
     };
 
     private final INetworkManagementService mNMService;
@@ -81,11 +88,8 @@
      */
     public static boolean requiresClat(NetworkAgentInfo nai) {
         // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
-        final int netType = nai.networkInfo.getType();
         final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
-        // TODO: this should also consider if the network is in SUSPENDED state to avoid stopping
-        // clatd in SUSPENDED state.
-        final boolean connected = nai.networkInfo.isConnected();
+        final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState());
         // We only run clat on networks that don't have a native IPv4 address.
         final boolean hasIPv4Address =
                 (nai.linkProperties != null) && nai.linkProperties.hasIPv4Address();
@@ -148,7 +152,6 @@
      * turn ND offload off if on WiFi.
      */
     private void enterRunningState() {
-        maybeSetIpv6NdOffload(mBaseIface, false);
         mState = State.RUNNING;
     }
 
@@ -156,10 +159,6 @@
      * Stop clatd, and turn ND offload on if it had been turned off.
      */
     private void enterStoppingState() {
-        if (isRunning()) {
-            maybeSetIpv6NdOffload(mBaseIface, true);
-        }
-
         try {
             mNMService.stopClatd(mBaseIface);
         } catch(RemoteException|IllegalStateException e) {
@@ -275,19 +274,6 @@
         }
     }
 
-    private void maybeSetIpv6NdOffload(String iface, boolean on) {
-        // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
-        if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
-            return;
-        }
-        try {
-            Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
-            mNMService.setInterfaceIpv6NdOffload(iface, on);
-        } catch(RemoteException|IllegalStateException e) {
-            Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
-        }
-    }
-
     /**
      * Adds stacked link on base link and transitions to RUNNING state.
      */
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 6f7ace2..6206dfc 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -38,6 +38,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.IndentingPrintWriter;
+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;
@@ -82,9 +83,8 @@
     private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
     // Ring buffer array for storing packet wake up events sent by Netd.
     @GuardedBy("this")
-    private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH];
-    @GuardedBy("this")
-    private long mWakeupEventCursor = 0;
+    private final RingBuffer<WakeupEvent> mWakeupEvents =
+            new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
 
     private final ConnectivityManager mCm;
 
@@ -170,18 +170,16 @@
             timestampMs = System.currentTimeMillis();
         }
 
-        addWakupEvent(iface, timestampMs, uid);
+        addWakeupEvent(iface, timestampMs, uid);
     }
 
     @GuardedBy("this")
-    private void addWakupEvent(String iface, long timestampMs, int uid) {
-        int index = wakeupEventIndex(mWakeupEventCursor);
-        mWakeupEventCursor++;
+    private void addWakeupEvent(String iface, long timestampMs, int uid) {
         WakeupEvent event = new WakeupEvent();
         event.iface = iface;
         event.timestampMs = timestampMs;
         event.uid = uid;
-        mWakeupEvents[index] = event;
+        mWakeupEvents.append(event);
         WakeupStats stats = mWakeupStats.get(iface);
         if (stats == null) {
             stats = new WakeupStats(iface);
@@ -190,23 +188,6 @@
         stats.countEvent(event);
     }
 
-    @GuardedBy("this")
-    private WakeupEvent[] getWakeupEvents() {
-        int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length);
-        WakeupEvent[] out = new WakeupEvent[length];
-        // Reverse iteration from youngest event to oldest event.
-        long inCursor = mWakeupEventCursor - 1;
-        int outIdx = out.length - 1;
-        while (outIdx >= 0) {
-            out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)];
-        }
-        return out;
-    }
-
-    private static int wakeupEventIndex(long cursor) {
-        return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH);
-    }
-
     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
         flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
         flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
@@ -230,7 +211,7 @@
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.println(mWakeupStats.valueAt(i));
         }
-        for (WakeupEvent wakeup : getWakeupEvents()) {
+        for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
             pw.println(wakeup);
         }
     }
diff --git a/com/android/server/connectivity/tethering/OffloadController.java b/com/android/server/connectivity/tethering/OffloadController.java
index 5eafe5f..cff216c 100644
--- a/com/android/server/connectivity/tethering/OffloadController.java
+++ b/com/android/server/connectivity/tethering/OffloadController.java
@@ -52,6 +52,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -73,6 +74,8 @@
     private static final String ANYIP = "0.0.0.0";
     private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
 
+    private static enum UpdateType { IF_NEEDED, FORCE };
+
     private final Handler mHandler;
     private final OffloadHardwareInterface mHwInterface;
     private final ContentResolver mContentResolver;
@@ -185,8 +188,8 @@
                         updateStatsForAllUpstreams();
                         forceTetherStatsPoll();
                         // [2] (Re)Push all state.
-                        // TODO: computeAndPushLocalPrefixes()
-                        // TODO: push all downstream state.
+                        computeAndPushLocalPrefixes(UpdateType.FORCE);
+                        pushAllDownstreamState();
                         pushUpstreamParameters(null);
                     }
 
@@ -319,7 +322,7 @@
     }
 
     private boolean maybeUpdateDataLimit(String iface) {
-        // setDataLimit may only be called while offload is occuring on this upstream.
+        // setDataLimit may only be called while offload is occurring on this upstream.
         if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
             return true;
         }
@@ -368,15 +371,15 @@
         // upstream parameters fails (probably just wait for a subsequent
         // onOffloadEvent() callback to tell us offload is available again and
         // then reapply all state).
-        computeAndPushLocalPrefixes();
+        computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
         pushUpstreamParameters(prevUpstream);
     }
 
     public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
-        if (!started()) return;
-
         mExemptPrefixes = localPrefixes;
-        computeAndPushLocalPrefixes();
+
+        if (!started()) return;
+        computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
     }
 
     public void notifyDownstreamLinkProperties(LinkProperties lp) {
@@ -385,27 +388,38 @@
         if (Objects.equals(oldLp, lp)) return;
 
         if (!started()) return;
+        pushDownstreamState(oldLp, lp);
+    }
 
-        final List<RouteInfo> oldRoutes = (oldLp != null) ? oldLp.getRoutes() : new ArrayList<>();
-        final List<RouteInfo> newRoutes = lp.getRoutes();
+    private void pushDownstreamState(LinkProperties oldLp, LinkProperties newLp) {
+        final String ifname = newLp.getInterfaceName();
+        final List<RouteInfo> oldRoutes =
+                (oldLp != null) ? oldLp.getRoutes() : Collections.EMPTY_LIST;
+        final List<RouteInfo> newRoutes = newLp.getRoutes();
 
         // For each old route, if not in new routes: remove.
-        for (RouteInfo oldRoute : oldRoutes) {
-            if (shouldIgnoreDownstreamRoute(oldRoute)) continue;
-            if (!newRoutes.contains(oldRoute)) {
-                mHwInterface.removeDownstreamPrefix(ifname, oldRoute.getDestination().toString());
+        for (RouteInfo ri : oldRoutes) {
+            if (shouldIgnoreDownstreamRoute(ri)) continue;
+            if (!newRoutes.contains(ri)) {
+                mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
             }
         }
 
         // For each new route, if not in old routes: add.
-        for (RouteInfo newRoute : newRoutes) {
-            if (shouldIgnoreDownstreamRoute(newRoute)) continue;
-            if (!oldRoutes.contains(newRoute)) {
-                mHwInterface.addDownstreamPrefix(ifname, newRoute.getDestination().toString());
+        for (RouteInfo ri : newRoutes) {
+            if (shouldIgnoreDownstreamRoute(ri)) continue;
+            if (!oldRoutes.contains(ri)) {
+                mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
             }
         }
     }
 
+    private void pushAllDownstreamState() {
+        for (LinkProperties lp : mDownstreams.values()) {
+            pushDownstreamState(null, lp);
+        }
+    }
+
     public void removeDownstreamInterface(String ifname) {
         final LinkProperties lp = mDownstreams.remove(ifname);
         if (lp == null) return;
@@ -484,10 +498,11 @@
         return success;
     }
 
-    private boolean computeAndPushLocalPrefixes() {
+    private boolean computeAndPushLocalPrefixes(UpdateType how) {
+        final boolean force = (how == UpdateType.FORCE);
         final Set<String> localPrefixStrs = computeLocalPrefixStrings(
                 mExemptPrefixes, mUpstreamLinkProperties);
-        if (mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
+        if (!force && mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
 
         mLastLocalPrefixStrs = localPrefixStrs;
         return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs));
@@ -581,9 +596,10 @@
         }
 
         mNatUpdateCallbacksReceived++;
+        final String natDescription = String.format("%s (%s, %s) -> (%s, %s)",
+                protoName, srcAddr, srcPort, dstAddr, dstPort);
         if (DBG) {
-            mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)",
-                     protoName, srcAddr, srcPort, dstAddr, dstPort));
+            mLog.log("NAT timeout update: " + natDescription);
         }
 
         final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
@@ -594,7 +610,7 @@
             NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
         } catch (ErrnoException e) {
             mNatUpdateNetlinkErrors++;
-            mLog.e("Error updating NAT conntrack entry: " + e
+            mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e
                     + ", msg: " + NetlinkConstants.hexify(msg));
             mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
             mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
diff --git a/com/android/server/content/SyncManager.java b/com/android/server/content/SyncManager.java
index 2f3b559..9cd52d7 100644
--- a/com/android/server/content/SyncManager.java
+++ b/com/android/server/content/SyncManager.java
@@ -46,8 +46,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.content.pm.RegisteredServicesCache;
 import android.content.pm.RegisteredServicesCacheListener;
@@ -143,6 +143,7 @@
 
     private static final boolean DEBUG_ACCOUNT_ACCESS = false;
 
+    // Only do the check on a debuggable build.
     private static final boolean ENABLE_SUSPICIOUS_CHECK = Build.IS_DEBUGGABLE;
 
     /** Delay a sync due to local changes this long. In milliseconds */
@@ -537,9 +538,11 @@
      * @return whether the device most likely has some periodic syncs.
      */
     private boolean likelyHasPeriodicSyncs() {
-        // STOPSHIP Remove the google specific string.
         try {
-            return AccountManager.get(mContext).getAccountsByType("com.google").length > 0;
+            // Each sync adapter has a daily periodic sync by default, but sync adapters can remove
+            // them by themselves. So here, we use an arbitrary threshold. If there are more than
+            // this many sync endpoints, surely one of them should have a periodic sync...
+            return mSyncStorageEngine.getAuthorityCount() >= 6;
         } catch (Throwable th) {
             // Just in case.
         }
@@ -3775,48 +3778,10 @@
         }
         if (op.isPeriodic) {
             mLogger.log("Removing periodic sync ", op, " for ", why);
-
-            if (ENABLE_SUSPICIOUS_CHECK && isSuspiciousPeriodicSyncRemoval(op)) {
-                wtfWithLog("Suspicious removal of " + op + " for " + why);
-            }
         }
         getJobScheduler().cancel(op.jobId);
     }
 
-    private boolean isSuspiciousPeriodicSyncRemoval(SyncOperation op) {
-        // STOPSHIP Remove the google specific string.
-        if (!op.isPeriodic){
-            return false;
-        }
-        boolean found = false;
-        for (UserInfo user : UserManager.get(mContext).getUsers(/*excludeDying=*/ true)) {
-            if (op.target.userId == user.id) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            return false; // User is being removed, okay.
-        }
-        switch (op.target.provider) {
-            case "gmail-ls":
-            case "com.android.contacts.metadata":
-                break;
-            default:
-                return false;
-        }
-        final Account account = op.target.account;
-        final Account[] accounts = AccountManager.get(mContext)
-                .getAccountsByTypeAsUser(account.type, UserHandle.of(op.target.userId));
-        for (Account a : accounts) {
-            if (a.equals(account)) {
-                return true; // Account still exists.  Suspicious!
-            }
-        }
-        // Account no longer exists. Makes sense...
-        return false;
-    }
-
     private void wtfWithLog(String message) {
         Slog.wtf(TAG, message);
         mLogger.log("WTF: ", message);
diff --git a/com/android/server/content/SyncStorageEngine.java b/com/android/server/content/SyncStorageEngine.java
index 7b277c0..3591871 100644
--- a/com/android/server/content/SyncStorageEngine.java
+++ b/com/android/server/content/SyncStorageEngine.java
@@ -911,6 +911,12 @@
         }
     }
 
+    public int getAuthorityCount() {
+        synchronized (mAuthorities) {
+            return mAuthorities.size();
+        }
+    }
+
     public AuthorityInfo getAuthority(int authorityId) {
         synchronized (mAuthorities) {
             return mAuthorities.get(authorityId);
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6c859f7..c59f44e 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -97,8 +97,8 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
@@ -5350,7 +5350,7 @@
         }
     }
 
-    private void forceWipeUser(int userId) {
+    private void forceWipeUser(int userId, String wipeReasonForUser) {
         try {
             IActivityManager am = mInjector.getIActivityManager();
             if (am.getCurrentUser().id == userId) {
@@ -5361,7 +5361,7 @@
             if (!userRemoved) {
                 Slog.w(LOG_TAG, "Couldn't remove user " + userId);
             } else if (isManagedProfile(userId)) {
-                sendWipeProfileNotification();
+                sendWipeProfileNotification(wipeReasonForUser);
             }
         } catch (RemoteException re) {
             // Shouldn't happen
@@ -5369,23 +5369,26 @@
     }
 
     @Override
-    public void wipeData(int flags) {
+    public void wipeDataWithReason(int flags, String wipeReasonForUser) {
         if (!mHasFeature) {
             return;
         }
+        Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
         enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
 
         final ActiveAdmin admin;
         synchronized (this) {
             admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
         }
-        String reason = "DevicePolicyManager.wipeData() from "
+        String internalReason = "DevicePolicyManager.wipeDataWithReason() from "
                 + admin.info.getComponent().flattenToShortString();
         wipeDataNoLock(
-                admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier());
+                admin.info.getComponent(), flags, internalReason, wipeReasonForUser,
+                admin.getUserHandle().getIdentifier());
     }
 
-    private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) {
+    private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
+                                String wipeReasonForUser, int userId) {
         wtfIfInLock();
 
         long ident = mInjector.binderClearCallingIdentity();
@@ -5420,25 +5423,26 @@
             // (rather than system), we should probably trigger factory reset. Current code just
             // removes that user (but still clears FRP...)
             if (userId == UserHandle.USER_SYSTEM) {
-                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
-                        reason, /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
+                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (
+                        flags & WIPE_EXTERNAL_STORAGE) != 0,
+                        internalReason,
+                        /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
             } else {
-                forceWipeUser(userId);
+                forceWipeUser(userId, wipeReasonForUser);
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
     }
 
-    private void sendWipeProfileNotification() {
-        String contentText = mContext.getString(R.string.work_profile_deleted_description_dpm_wipe);
+    private void sendWipeProfileNotification(String wipeReasonForUser) {
         Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
                         .setContentTitle(mContext.getString(R.string.work_profile_deleted))
-                        .setContentText(contentText)
+                        .setContentText(wipeReasonForUser)
                         .setColor(mContext.getColor(R.color.system_notification_accent_color))
-                        .setStyle(new Notification.BigTextStyle().bigText(contentText))
+                        .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser))
                         .build();
         mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification);
     }
@@ -5610,9 +5614,12 @@
             // able to do so).
             // IMPORTANT: Call without holding the lock to prevent deadlock.
             try {
+                String wipeReasonForUser = mContext.getString(
+                        R.string.work_profile_deleted_reason_maximum_password_failure);
                 wipeDataNoLock(strictestAdmin.info.getComponent(),
                         /*flags=*/ 0,
                         /*reason=*/ "reportFailedPasswordAttempt()",
+                        wipeReasonForUser,
                         userId);
             } catch (SecurityException e) {
                 Slog.w(LOG_TAG, "Failed to wipe user " + userId
@@ -5621,7 +5628,8 @@
         }
 
         if (mInjector.securityLogIsLoggingEnabled()) {
-            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
+            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
+                    /*result*/ 0,
                     /*method strength*/ 1);
         }
     }
diff --git a/com/android/server/display/DisplayDeviceInfo.java b/com/android/server/display/DisplayDeviceInfo.java
index ef6de4c..fddb81b 100644
--- a/com/android/server/display/DisplayDeviceInfo.java
+++ b/com/android/server/display/DisplayDeviceInfo.java
@@ -98,6 +98,12 @@
     public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 9;
 
     /**
+     * Flag: This display will destroy its content on removal.
+     * @hide
+     */
+    public static final int FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 10;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
diff --git a/com/android/server/display/LocalDisplayAdapter.java b/com/android/server/display/LocalDisplayAdapter.java
index 8756484..d61a418 100644
--- a/com/android/server/display/LocalDisplayAdapter.java
+++ b/com/android/server/display/LocalDisplayAdapter.java
@@ -473,17 +473,18 @@
                         }
 
                         // If the state change was from or to VR, then we need to tell the light
-                        // so that it can apply appropriate VR brightness settings. This should
-                        // happen prior to changing the brightness but also if there is no
-                        // brightness change at all.
+                        // so that it can apply appropriate VR brightness settings. Also, update the
+                        // brightness so the state is propogated to light.
+                        boolean vrModeChange = false;
                         if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
                                 currentState != state) {
                             setVrMode(state == Display.STATE_VR);
+                            vrModeChange = true;
                         }
 
 
                         // Apply brightness changes given that we are in a non-suspended state.
-                        if (brightnessChanged) {
+                        if (brightnessChanged || vrModeChange) {
                             setDisplayBrightness(brightness);
                         }
 
diff --git a/com/android/server/display/LogicalDisplay.java b/com/android/server/display/LogicalDisplay.java
index addad0b..78a5407 100644
--- a/com/android/server/display/LogicalDisplay.java
+++ b/com/android/server/display/LogicalDisplay.java
@@ -238,6 +238,9 @@
                 // For private displays by default content is destroyed on removal.
                 mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+                mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
+            }
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRESENTATION) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION;
             }
diff --git a/com/android/server/display/NightDisplayService.java b/com/android/server/display/NightDisplayService.java
index aafc631..9cf1367 100644
--- a/com/android/server/display/NightDisplayService.java
+++ b/com/android/server/display/NightDisplayService.java
@@ -48,8 +48,10 @@
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
 
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.Calendar;
 import java.util.TimeZone;
 
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
@@ -306,7 +308,7 @@
     }
 
     @Override
-    public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+    public void onCustomStartTimeChanged(LocalTime startTime) {
         Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
 
         if (mAutoMode != null) {
@@ -315,7 +317,7 @@
     }
 
     @Override
-    public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+    public void onCustomEndTimeChanged(LocalTime endTime) {
         Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
 
         if (mAutoMode != null) {
@@ -414,6 +416,36 @@
         outTemp[10] = blue;
     }
 
+    /**
+     * Returns the first date time corresponding to the local time that occurs before the
+     * provided date time.
+     *
+     * @param compareTime the LocalDateTime to compare against
+     * @return the prior LocalDateTime corresponding to this local time
+     */
+    public static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
+        final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
+                compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
+
+        // Check if the local time has passed, if so return the same time yesterday.
+        return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
+    }
+
+    /**
+     * Returns the first date time corresponding to this local time that occurs after the
+     * provided date time.
+     *
+     * @param compareTime the LocalDateTime to compare against
+     * @return the next LocalDateTime corresponding to this local time
+     */
+    public static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
+        final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
+                compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
+
+        // Check if the local time has passed, if so return the same time tomorrow.
+        return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
+    }
+
     private abstract class AutoMode implements NightDisplayController.Callback {
         public abstract void onStart();
 
@@ -425,10 +457,10 @@
         private final AlarmManager mAlarmManager;
         private final BroadcastReceiver mTimeChangedReceiver;
 
-        private NightDisplayController.LocalTime mStartTime;
-        private NightDisplayController.LocalTime mEndTime;
+        private LocalTime mStartTime;
+        private LocalTime mEndTime;
 
-        private Calendar mLastActivatedTime;
+        private LocalDateTime mLastActivatedTime;
 
         CustomAutoMode() {
             mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
@@ -441,31 +473,15 @@
         }
 
         private void updateActivated() {
-            final Calendar now = Calendar.getInstance();
-            final Calendar startTime = mStartTime.getDateTimeBefore(now);
-            final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
+            final LocalDateTime now = LocalDateTime.now();
+            final LocalDateTime start = getDateTimeBefore(mStartTime, now);
+            final LocalDateTime end = getDateTimeAfter(mEndTime, start);
+            boolean activate = now.isBefore(end);
 
-            boolean activate = now.before(endTime);
             if (mLastActivatedTime != null) {
-                // Convert mLastActivatedTime to the current timezone if needed.
-                final TimeZone currentTimeZone = now.getTimeZone();
-                if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
-                    final int year = mLastActivatedTime.get(Calendar.YEAR);
-                    final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
-                    final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
-                    final int minute = mLastActivatedTime.get(Calendar.MINUTE);
-
-                    mLastActivatedTime.setTimeZone(currentTimeZone);
-                    mLastActivatedTime.set(Calendar.YEAR, year);
-                    mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
-                    mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
-                    mLastActivatedTime.set(Calendar.MINUTE, minute);
-                }
-
                 // Maintain the existing activated state if within the current period.
-                if (mLastActivatedTime.before(now)
-                        && mLastActivatedTime.after(startTime)
-                        && (mLastActivatedTime.after(endTime) || now.before(endTime))) {
+                if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
+                        && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
                     activate = mController.isActivated();
                 }
             }
@@ -473,14 +489,16 @@
             if (mIsActivated == null || mIsActivated != activate) {
                 mController.setActivated(activate);
             }
+
             updateNextAlarm(mIsActivated, now);
         }
 
-        private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
+        private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
             if (activated != null) {
-                final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
-                        : mStartTime.getDateTimeAfter(now);
-                mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
+                final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
+                        : getDateTimeAfter(mStartTime, now);
+                final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+                mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
             }
         }
 
@@ -510,18 +528,18 @@
         @Override
         public void onActivated(boolean activated) {
             mLastActivatedTime = mController.getLastActivatedTime();
-            updateNextAlarm(activated, Calendar.getInstance());
+            updateNextAlarm(activated, LocalDateTime.now());
         }
 
         @Override
-        public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+        public void onCustomStartTimeChanged(LocalTime startTime) {
             mStartTime = startTime;
             mLastActivatedTime = null;
             updateActivated();
         }
 
         @Override
-        public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+        public void onCustomEndTimeChanged(LocalTime endTime) {
             mEndTime = endTime;
             mLastActivatedTime = null;
             updateActivated();
@@ -550,15 +568,14 @@
             }
 
             boolean activate = state.isNight();
-            final Calendar lastActivatedTime = mController.getLastActivatedTime();
+            final LocalDateTime lastActivatedTime = mController.getLastActivatedTime();
             if (lastActivatedTime != null) {
-                final Calendar now = Calendar.getInstance();
-                final Calendar sunrise = state.sunrise();
-                final Calendar sunset = state.sunset();
-
+                final LocalDateTime now = LocalDateTime.now();
+                final LocalDateTime sunrise = state.sunrise();
+                final LocalDateTime sunset = state.sunset();
                 // Maintain the existing activated state if within the current period.
-                if (lastActivatedTime.before(now)
-                        && (lastActivatedTime.after(sunrise) ^ lastActivatedTime.after(sunset))) {
+                if (lastActivatedTime.isBefore(now) && (lastActivatedTime.isBefore(sunrise)
+                        ^ lastActivatedTime.isBefore(sunset))) {
                     activate = mController.isActivated();
                 }
             }
diff --git a/com/android/server/display/VirtualDisplayAdapter.java b/com/android/server/display/VirtualDisplayAdapter.java
index d6ab888..f86d576 100644
--- a/com/android/server/display/VirtualDisplayAdapter.java
+++ b/com/android/server/display/VirtualDisplayAdapter.java
@@ -24,6 +24,8 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+import static android.hardware.display.DisplayManager
+        .VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
 
 import android.content.Context;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -363,6 +365,9 @@
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+                  mInfo.flags |= DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL;
+                }
 
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
diff --git a/com/android/server/fingerprint/FingerprintService.java b/com/android/server/fingerprint/FingerprintService.java
index b1c165e..1df9c86 100644
--- a/com/android/server/fingerprint/FingerprintService.java
+++ b/com/android/server/fingerprint/FingerprintService.java
@@ -63,6 +63,8 @@
 import android.service.fingerprint.FingerprintServiceDumpProto;
 import android.service.fingerprint.FingerprintUserStatsProto;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -81,7 +83,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -89,18 +90,19 @@
 /**
  * A service to manage multiple clients that want to access the fingerprint HAL API.
  * The service is responsible for maintaining a list of clients and dispatching all
- * fingerprint -related events.
+ * fingerprint-related events.
  *
  * @hide
  */
 public class FingerprintService extends SystemService implements IHwBinder.DeathRecipient {
     static final String TAG = "FingerprintService";
     static final boolean DEBUG = true;
-    private static final boolean CLEANUP_UNUSED_FP = false;
+    private static final boolean CLEANUP_UNUSED_FP = true;
     private static final String FP_DATA_DIR = "fpdata";
     private static final int MSG_USER_SWITCHING = 10;
     private static final String ACTION_LOCKOUT_RESET =
             "com.android.server.fingerprint.ACTION_LOCKOUT_RESET";
+    private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
 
     private class PerformanceStats {
         int accept; // number of accepted fingerprints
@@ -128,8 +130,8 @@
     private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
     private Context mContext;
     private long mHalDeviceId;
-    private boolean mTimedLockoutCleared;
-    private int mFailedAttempts;
+    private SparseBooleanArray mTimedLockoutCleared;
+    private SparseIntArray mFailedAttempts;
     @GuardedBy("this")
     private IBiometricsFingerprint mDaemon;
     private final PowerManager mPowerManager;
@@ -139,10 +141,8 @@
     private ClientMonitor mPendingClient;
     private PerformanceStats mPerformanceStats;
 
-
     private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
-    private LinkedList<Integer> mEnumeratingUserIds = new LinkedList<>();
-    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw finterprints
+    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
 
     private class UserFingerprint {
         Fingerprint f;
@@ -177,15 +177,17 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
-                resetFailedAttempts(false /* clearAttemptCounter */);
+                final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
+                resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
             }
         }
     };
 
-    private final Runnable mResetFailedAttemptsRunnable = new Runnable() {
+    private final Runnable mResetFailedAttemptsForCurrentUserRunnable = new Runnable() {
         @Override
         public void run() {
-            resetFailedAttempts(true /* clearAttemptCounter */);
+            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                    ActivityManager.getCurrentUser());
         }
     };
 
@@ -221,6 +223,8 @@
         mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
                 RESET_FINGERPRINT_LOCKOUT, null /* handler */);
         mUserManager = UserManager.get(mContext);
+        mTimedLockoutCleared = new SparseBooleanArray();
+        mFailedAttempts = new SparseIntArray();
     }
 
     @Override
@@ -233,7 +237,7 @@
 
     public synchronized IBiometricsFingerprint getFingerprintDaemon() {
         if (mDaemon == null) {
-            Slog.v(TAG, "mDeamon was null, reconnect to fingerprint");
+            Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
             try {
                 mDaemon = IBiometricsFingerprint.getService();
             } catch (java.util.NoSuchElementException e) {
@@ -259,7 +263,7 @@
             if (mHalDeviceId != 0) {
                 loadAuthenticatorIds();
                 updateActiveGroup(ActivityManager.getCurrentUser(), null);
-                doFingerprintCleanup(ActivityManager.getCurrentUser());
+                doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
             } else {
                 Slog.w(TAG, "Failed to open Fingerprint HAL!");
                 MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
@@ -288,52 +292,41 @@
         }
     }
 
-    private void doFingerprintCleanup(int userId) {
+    /**
+     * This method should be called upon connection to the daemon, and when user switches.
+     * @param userId
+     */
+    private void doFingerprintCleanupForUser(int userId) {
         if (CLEANUP_UNUSED_FP) {
-            resetEnumerateState();
-            mEnumeratingUserIds.push(userId);
-            enumerateNextUser();
+            enumerateUser(userId);
         }
     }
 
-    private void resetEnumerateState() {
-        if (DEBUG) Slog.v(TAG, "Enumerate cleaning up");
-        mEnumeratingUserIds.clear();
+    private void clearEnumerateState() {
+        if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
         mUnknownFingerprints.clear();
     }
 
-    private void enumerateNextUser() {
-        int nextUser = mEnumeratingUserIds.getFirst();
-        updateActiveGroup(nextUser, null);
+    private void enumerateUser(int userId) {
+        if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
         boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-
-        if (DEBUG) Slog.v(TAG, "Enumerating user id " + nextUser + " of "
-                + mEnumeratingUserIds.size() + " remaining users");
-
-        startEnumerate(mToken, nextUser, null, restricted, true /* internal */);
+        startEnumerate(mToken, userId, null, restricted, true /* internal */);
     }
 
     // Remove unknown fingerprints from hardware
     private void cleanupUnknownFingerprints() {
         if (!mUnknownFingerprints.isEmpty()) {
-            Slog.w(TAG, "unknown fingerprint size: " + mUnknownFingerprints.size());
             UserFingerprint uf = mUnknownFingerprints.get(0);
             mUnknownFingerprints.remove(uf);
             boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-            updateActiveGroup(uf.userId, null);
             startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
                     restricted, true /* internal */);
         } else {
-            resetEnumerateState();
+            clearEnumerateState();
         }
     }
 
     protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId
-                + ", gid=" + groupId
-                + ", dev=" + deviceId
-                + ", rem=" + remaining);
-
         ClientMonitor client = mCurrentClient;
 
         if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
@@ -343,24 +336,21 @@
 
         // All fingerprints in hardware for this user were enumerated
         if (remaining == 0) {
-            mEnumeratingUserIds.poll();
-
             if (client instanceof InternalEnumerateClient) {
-                List<Fingerprint> enrolled = ((InternalEnumerateClient) client).getEnumeratedList();
-                Slog.w(TAG, "Added " + enrolled.size() + " enumerated fingerprints for deletion");
-                for (Fingerprint f : enrolled) {
+                List<Fingerprint> unknownFingerprints =
+                        ((InternalEnumerateClient) client).getUnknownFingerprints();
+
+                if (!unknownFingerprints.isEmpty()) {
+                    Slog.w(TAG, "Adding " + unknownFingerprints.size() +
+                            " fingerprints for deletion");
+                }
+                for (Fingerprint f : unknownFingerprints) {
                     mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
                 }
-            }
-
-            removeClient(client);
-
-            if (!mEnumeratingUserIds.isEmpty()) {
-                enumerateNextUser();
-            } else if (client instanceof InternalEnumerateClient) {
-                if (DEBUG) Slog.v(TAG, "Finished enumerating all users");
-                // This will start a chain of InternalRemovalClients
+                removeClient(client);
                 cleanupUnknownFingerprints();
+            } else {
+                removeClient(client);
             }
         }
     }
@@ -368,7 +358,7 @@
     protected void handleError(long deviceId, int error, int vendorCode) {
         ClientMonitor client = mCurrentClient;
         if (client instanceof InternalRemovalClient || client instanceof InternalEnumerateClient) {
-            resetEnumerateState();
+            clearEnumerateState();
         }
         if (client != null && client.onError(error, vendorCode)) {
             removeClient(client);
@@ -412,7 +402,7 @@
         if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
             cleanupUnknownFingerprints();
         } else if (client instanceof InternalRemovalClient){
-            resetEnumerateState();
+            clearEnumerateState();
         }
     }
 
@@ -466,8 +456,14 @@
     }
 
     void handleUserSwitching(int userId) {
+        if (mCurrentClient instanceof InternalRemovalClient
+                || mCurrentClient instanceof InternalEnumerateClient) {
+            Slog.w(TAG, "User switched while performing cleanup");
+            removeClient(mCurrentClient);
+            clearEnumerateState();
+        }
         updateActiveGroup(userId, null);
-        doFingerprintCleanup(userId);
+        doFingerprintCleanupForUser(userId);
     }
 
     private void removeClient(ClientMonitor client) {
@@ -488,27 +484,32 @@
     }
 
     private int getLockoutMode() {
-        if (mFailedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
+        final int currentUser = ActivityManager.getCurrentUser();
+        final int failedAttempts = mFailedAttempts.get(currentUser, 0);
+        if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
             return AuthenticationClient.LOCKOUT_PERMANENT;
-        } else if (mFailedAttempts > 0 && mTimedLockoutCleared == false &&
-                (mFailedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
+        } else if (failedAttempts > 0 &&
+                mTimedLockoutCleared.get(currentUser, false) == false
+                && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
             return AuthenticationClient.LOCKOUT_TIMED;
         }
         return AuthenticationClient.LOCKOUT_NONE;
     }
 
-    private void scheduleLockoutReset() {
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent());
+    private void scheduleLockoutResetForUser(int userId) {
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+                getLockoutResetIntentForUser(userId));
     }
 
-    private void cancelLockoutReset() {
-        mAlarmManager.cancel(getLockoutResetIntent());
+    private void cancelLockoutResetForUser(int userId) {
+        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
     }
 
-    private PendingIntent getLockoutResetIntent() {
-        return PendingIntent.getBroadcast(mContext, 0,
-                new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT);
+    private PendingIntent getLockoutResetIntentForUser(int userId) {
+        return PendingIntent.getBroadcast(mContext, userId,
+                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+                PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     public long startPreEnroll(IBinder token) {
@@ -555,6 +556,12 @@
                 // This condition means we're currently running internal diagnostics to
                 // remove extra fingerprints in the hardware and/or the software
                 // TODO: design an escape hatch in case client never finishes
+                if (newClient != null) {
+                    Slog.w(TAG, "Internal cleanup in progress but trying to start client "
+                            + newClient.getClass().getSuperclass().getSimpleName()
+                            + "(" + newClient.getOwnerString() + ")"
+                            + ", initiatedByClient = " + initiatedByClient);
+                }
             }
             else {
                 currentClient.stop(initiatedByClient);
@@ -567,7 +574,7 @@
             if (DEBUG) Slog.v(TAG, "starting client "
                     + newClient.getClass().getSuperclass().getSimpleName()
                     + "(" + newClient.getOwnerString() + ")"
-                    + ", initiatedByClient = " + initiatedByClient + ")");
+                    + ", initiatedByClient = " + initiatedByClient);
             notifyClientActiveCallbacks(true);
 
             newClient.start();
@@ -813,8 +820,9 @@
                 receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) {
             @Override
             public int handleFailedAttempt() {
-                mFailedAttempts++;
-                mTimedLockoutCleared = false;
+                final int currentUser = ActivityManager.getCurrentUser();
+                mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
+                mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);
                 final int lockoutMode = getLockoutMode();
                 if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
                     mPerformanceStats.permanentLockout++;
@@ -824,7 +832,7 @@
 
                 // Failing multiple times will continue to push out the lockout time
                 if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
-                    scheduleLockoutReset();
+                    scheduleLockoutResetForUser(currentUser);
                     return lockoutMode;
                 }
                 return AuthenticationClient.LOCKOUT_NONE;
@@ -832,7 +840,8 @@
 
             @Override
             public void resetFailedAttempts() {
-                FingerprintService.this.resetFailedAttempts(true /* clearAttemptCounter */);
+                FingerprintService.this.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                        ActivityManager.getCurrentUser());
             }
 
             @Override
@@ -886,17 +895,17 @@
 
     // attempt counter should only be cleared when Keyguard goes away or when
     // a fingerprint is successfully authenticated
-    protected void resetFailedAttempts(boolean clearAttemptCounter) {
+    protected void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
         if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
             Slog.v(TAG, "Reset fingerprint lockout, clearAttemptCounter=" + clearAttemptCounter);
         }
         if (clearAttemptCounter) {
-            mFailedAttempts = 0;
+            mFailedAttempts.put(userId, 0);
         }
-        mTimedLockoutCleared = true;
+        mTimedLockoutCleared.put(userId, true);
         // If we're asked to reset failed attempts externally (i.e. from Keyguard),
         // the alarm might still be pending; remove it.
-        cancelLockoutReset();
+        cancelLockoutResetForUser(userId);
         notifyLockoutResetMonitors();
     }
 
@@ -1277,7 +1286,7 @@
         public void resetTimeout(byte [] token) {
             checkPermission(RESET_FINGERPRINT_LOCKOUT);
             // TODO: confirm security token when we move timeout management into the HAL layer.
-            mHandler.post(mResetFailedAttemptsRunnable);
+            mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
         }
 
         @Override
@@ -1338,6 +1347,8 @@
                 set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
                 set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
                 set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
+                set.put("permanentLockoutCrypto",
+                    (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
                 sets.put(set);
             }
 
@@ -1367,7 +1378,7 @@
                 proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
                 proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
                 proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.lockout);
+                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.permanentLockout);
                 proto.end(countsToken);
             }
 
@@ -1380,7 +1391,7 @@
                 proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
                 proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
                 proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.lockout);
+                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.permanentLockout);
                 proto.end(countsToken);
             }
 
diff --git a/com/android/server/fingerprint/InternalEnumerateClient.java b/com/android/server/fingerprint/InternalEnumerateClient.java
index 88d9ef4..434db98 100644
--- a/com/android/server/fingerprint/InternalEnumerateClient.java
+++ b/com/android/server/fingerprint/InternalEnumerateClient.java
@@ -30,7 +30,7 @@
 public abstract class InternalEnumerateClient extends EnumerateClient {
 
     private List<Fingerprint> mEnrolledList;
-    private List<Fingerprint> mEnumeratedList = new ArrayList<>(); // list of fp to delete
+    private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
 
     public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
             IFingerprintServiceReceiver receiver, int groupId, int userId,
@@ -47,7 +47,6 @@
             if (mEnrolledList.get(i).getFingerId() == fingerId) {
                 mEnrolledList.remove(i);
                 matched = true;
-                Slog.e(TAG, "Matched fingerprint fid=" + fingerId);
                 break;
             }
         }
@@ -55,7 +54,7 @@
         // fingerId 0 means no fingerprints are in hardware
         if (!matched && fingerId != 0) {
             Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
-            mEnumeratedList.add(fingerprint);
+            mUnknownFingerprints.add(fingerprint);
         }
     }
 
@@ -76,8 +75,8 @@
         mEnrolledList.clear();
     }
 
-    public List<Fingerprint> getEnumeratedList() {
-        return mEnumeratedList;
+    public List<Fingerprint> getUnknownFingerprints() {
+        return mUnknownFingerprints;
     }
 
     @Override
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index ac80794..78aa2f9 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -795,18 +795,22 @@
      * @param uid Uid to check against for removal of a job.
      *
      */
-    public void cancelJobsForUid(int uid, String reason) {
+    public boolean cancelJobsForUid(int uid, String reason) {
         if (uid == Process.SYSTEM_UID) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
-            return;
+            return false;
         }
+
+        boolean jobsCanceled = false;
         synchronized (mLock) {
             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
             for (int i=0; i<jobsForUid.size(); i++) {
                 JobStatus toRemove = jobsForUid.get(i);
                 cancelJobImplLocked(toRemove, null, reason);
+                jobsCanceled = true;
             }
         }
+        return jobsCanceled;
     }
 
     /**
@@ -816,13 +820,14 @@
      * @param uid Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    public void cancelJob(int uid, int jobId) {
+    public boolean cancelJob(int uid, int jobId) {
         JobStatus toCancel;
         synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
             if (toCancel != null) {
                 cancelJobImplLocked(toCancel, null, "cancel() called by app");
             }
+            return (toCancel != null);
         }
     }
 
@@ -2147,6 +2152,39 @@
         return 0;
     }
 
+    // Shell command infrastructure: cancel a scheduled job
+    int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+            boolean hasJobId, int jobId) {
+        if (DEBUG) {
+            Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
+        }
+
+        int pkgUid = -1;
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            pkgUid = pm.getPackageUid(pkgName, 0, userId);
+        } catch (RemoteException e) { /* can't happen */ }
+
+        if (pkgUid < 0) {
+            pw.println("Package " + pkgName + " not found.");
+            return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+        }
+
+        if (!hasJobId) {
+            pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
+            if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+                pw.println("No matching jobs found.");
+            }
+        } else {
+            pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
+            if (!cancelJob(pkgUid, jobId)) {
+                pw.println("No matching job found.");
+            }
+        }
+
+        return 0;
+    }
+
     void setMonitorBattery(boolean enabled) {
         synchronized (mLock) {
             if (mBatteryController != null) {
diff --git a/com/android/server/job/JobSchedulerShellCommand.java b/com/android/server/job/JobSchedulerShellCommand.java
index a53c088..d630aab 100644
--- a/com/android/server/job/JobSchedulerShellCommand.java
+++ b/com/android/server/job/JobSchedulerShellCommand.java
@@ -48,6 +48,8 @@
                     return runJob(pw);
                 case "timeout":
                     return timeout(pw);
+                case "cancel":
+                    return cancelJob(pw);
                 case "monitor-battery":
                     return monitorBattery(pw);
                 case "get-battery-seq":
@@ -205,6 +207,42 @@
         }
     }
 
+    private int cancelJob(PrintWriter pw) throws Exception {
+        checkPermission("cancel jobs");
+
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        if (userId < 0) {
+            pw.println("Error: must specify a concrete user ID");
+            return -1;
+        }
+
+        final String pkgName = getNextArg();
+        final String jobIdStr = getNextArg();
+        final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int monitorBattery(PrintWriter pw) throws Exception {
         checkPermission("change battery monitoring");
         String opt = getNextArgRequired();
@@ -315,6 +353,12 @@
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         all users");
+        pw.println("  cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+        pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
+        pw.println("    by that package will be canceled.  USE WITH CAUTION.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
         pw.println("  monitor-battery [on|off]");
         pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
         pw.println("    on makes get-battery-seq useful.");
diff --git a/com/android/server/locksettings/LockSettingsService.java b/com/android/server/locksettings/LockSettingsService.java
index 11043bd..a1a0106 100644
--- a/com/android/server/locksettings/LockSettingsService.java
+++ b/com/android/server/locksettings/LockSettingsService.java
@@ -376,7 +376,7 @@
         }
 
         public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
-            return new SyntheticPasswordManager(storage, getUserManager());
+            return new SyntheticPasswordManager(getContext(), storage, getUserManager());
         }
 
         public int binderGetCallingUid() {
@@ -763,7 +763,8 @@
     private void migrateOldDataAfterSystemReady() {
         try {
             // Migrate the FRP credential to the persistent data block
-            if (LockPatternUtils.frpCredentialEnabled() && !getBoolean("migrated_frp", false, 0)) {
+            if (LockPatternUtils.frpCredentialEnabled(mContext)
+                    && !getBoolean("migrated_frp", false, 0)) {
                 migrateFrpCredential();
                 setBoolean("migrated_frp", true, 0);
                 Slog.i(TAG, "Migrated migrated_frp.");
@@ -784,7 +785,7 @@
             return;
         }
         for (UserInfo userInfo : mUserManager.getUsers()) {
-            if (userOwnsFrpCredential(userInfo) && isUserSecure(userInfo.id)) {
+            if (userOwnsFrpCredential(mContext, userInfo) && isUserSecure(userInfo.id)) {
                 synchronized (mSpManager) {
                     if (isSyntheticPasswordBasedCredentialLocked(userInfo.id)) {
                         int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
@@ -2366,6 +2367,13 @@
                 Slog.w(TAG, "Invalid escrow token supplied");
                 return false;
             }
+            if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+                // Most likely, an untrusted credential reset happened in the past which
+                // changed the synthetic password
+                Slog.e(TAG, "Obsolete token: synthetic password derived but it fails GK "
+                        + "verification.");
+                return false;
+            }
             // Update PASSWORD_TYPE_KEY since it's needed by notifyActivePasswordMetricsAvailable()
             // called by setLockCredentialWithAuthTokenLocked().
             // TODO: refactor usage of PASSWORD_TYPE_KEY b/65239740
@@ -2497,7 +2505,7 @@
         }
 
         public void onSystemReady() {
-            if (frpCredentialEnabled()) {
+            if (frpCredentialEnabled(mContext)) {
                 updateRegistration();
             } else {
                 // If we don't intend to use frpCredentials and we're not provisioned yet, send
@@ -2526,7 +2534,7 @@
         private void clearFrpCredentialIfOwnerNotSecure() {
             List<UserInfo> users = mUserManager.getUsers();
             for (UserInfo user : users) {
-                if (userOwnsFrpCredential(user)) {
+                if (userOwnsFrpCredential(mContext, user)) {
                     if (!isUserSecure(user.id)) {
                         mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
                                 0, null);
diff --git a/com/android/server/locksettings/LockSettingsStrongAuth.java b/com/android/server/locksettings/LockSettingsStrongAuth.java
index 542b929..c9c9329 100644
--- a/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -27,6 +27,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.IStrongAuthTracker;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.DeadObjectException;
@@ -74,7 +75,10 @@
     }
 
     public void systemReady() {
-        mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+        }
     }
 
     private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 33a9a99..9440f17 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.weaver.V1_0.IWeaver;
 import android.hardware.weaver.V1_0.WeaverConfig;
@@ -255,13 +256,16 @@
         byte[] aggregatedSecret;
     }
 
+    private final Context mContext;
     private LockSettingsStorage mStorage;
     private IWeaver mWeaver;
     private WeaverConfig mWeaverConfig;
 
     private final UserManager mUserManager;
 
-    public SyntheticPasswordManager(LockSettingsStorage storage, UserManager userManager) {
+    public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
+            UserManager userManager) {
+        mContext = context;
         mStorage = storage;
         mUserManager = userManager;
     }
@@ -645,7 +649,7 @@
 
     public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(userInfo)) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
             PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
                     userInfo.id));
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
@@ -662,7 +666,8 @@
     private void synchronizeFrpPassword(PasswordData pwd,
             int requestedQuality, int userId) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext,
+                mUserManager.getUserInfo(userId))) {
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
                         pwd.toBytes());
@@ -675,7 +680,8 @@
     private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
             int weaverSlot) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext,
+                mUserManager.getUserInfo(userId))) {
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
                         requestedQuality, pwd.toBytes());
diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java
index 3795b7f..1cfd5f0 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -271,14 +271,6 @@
 
     // Binder call
     @Override
-    public boolean isGlobalBluetoothA2doOn() {
-        synchronized (mLock) {
-            return mGlobalBluetoothA2dpOn;
-        }
-    }
-
-    // Binder call
-    @Override
     public void setDiscoveryRequest(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         if (client == null) {
@@ -383,7 +375,7 @@
             synchronized (mLock) {
                 a2dpOn = mGlobalBluetoothA2dpOn;
             }
-            Slog.v(TAG, "restoreBluetoothA2dp( " + a2dpOn + ")");
+            Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
             mAudioService.setBluetoothA2dpOn(a2dpOn);
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
diff --git a/com/android/server/media/MediaSessionRecord.java b/com/android/server/media/MediaSessionRecord.java
index 89e1050..0b11479 100644
--- a/com/android/server/media/MediaSessionRecord.java
+++ b/com/android/server/media/MediaSessionRecord.java
@@ -462,18 +462,25 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                if (useSuggested) {
-                    if (AudioSystem.isStreamActive(stream, 0)) {
-                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
-                                flags, packageName, uid);
+                try {
+                    if (useSuggested) {
+                        if (AudioSystem.isStreamActive(stream, 0)) {
+                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream,
+                                    direction, flags, packageName, uid);
+                        } else {
+                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
+                                    AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
+                                    flags | previousFlagPlaySound, packageName, uid);
+                        }
                     } else {
-                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
-                                AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
-                                flags | previousFlagPlaySound, packageName, uid);
+                        mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
+                                packageName, uid);
                     }
-                } else {
-                    mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
-                            packageName, uid);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
+                            + stream + ", flags=" + flags + ", packageName=" + packageName
+                            + ", uid=" + uid + ", useSuggested=" + useSuggested
+                            + ", previousFlagPlaySound=" + previousFlagPlaySound, e);
                 }
             }
         });
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index b77ed91..b9a2d18 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -1363,6 +1363,10 @@
                                     flags, packageName, TAG);
                         } catch (RemoteException e) {
                             Log.e(TAG, "Error adjusting default volume.", e);
+                        } catch (IllegalArgumentException e) {
+                            Log.e(TAG, "Cannot adjust volume: direction=" + direction
+                                    + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
+                                    e);
                         }
                     }
                 });
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index 90dab2c..b4056b3 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -331,7 +331,6 @@
     private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
     private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
     private static final int MSG_POLICIES_CHANGED = 13;
-    private static final int MSG_SET_FIREWALL_RULES = 14;
     private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
@@ -3138,9 +3137,9 @@
                     uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
                 }
             }
-            setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
+            setUidFirewallRulesUL(chain, uidRules, CHAIN_TOGGLE_ENABLE);
         } else {
-            setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
+            setUidFirewallRulesUL(chain, null, CHAIN_TOGGLE_DISABLE);
         }
     }
 
@@ -3207,7 +3206,7 @@
                 }
             }
 
-            setUidFirewallRulesAsync(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
+            setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -3906,18 +3905,6 @@
                     removeInterfaceQuota((String) msg.obj);
                     return true;
                 }
-                case MSG_SET_FIREWALL_RULES: {
-                    final int chain = msg.arg1;
-                    final int toggle = msg.arg2;
-                    final SparseIntArray uidRules = (SparseIntArray) msg.obj;
-                    if (uidRules != null) {
-                        setUidFirewallRules(chain, uidRules);
-                    }
-                    if (toggle != CHAIN_TOGGLE_NONE) {
-                        enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
-                    }
-                    return true;
-                }
                 case MSG_RESET_FIREWALL_RULES_BY_UID: {
                     resetUidFirewallRules(msg.arg1);
                     return true;
@@ -4063,15 +4050,20 @@
 
     /**
      * Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
-     * {@link #enableFirewallChainUL(int, boolean)} asynchronously.
+     * {@link #enableFirewallChainUL(int, boolean)} synchronously.
      *
      * @param chain firewall chain.
      * @param uidRules new UID rules; if {@code null}, only toggles chain state.
      * @param toggle whether the chain should be enabled, disabled, or not changed.
      */
-    private void setUidFirewallRulesAsync(int chain, @Nullable SparseIntArray uidRules,
+    private void setUidFirewallRulesUL(int chain, @Nullable SparseIntArray uidRules,
             @ChainToggleType int toggle) {
-        mHandler.obtainMessage(MSG_SET_FIREWALL_RULES, chain, toggle, uidRules).sendToTarget();
+        if (uidRules != null) {
+            setUidFirewallRulesUL(chain, uidRules);
+        }
+        if (toggle != CHAIN_TOGGLE_NONE) {
+            enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
+        }
     }
 
     /**
@@ -4079,7 +4071,7 @@
      * here to netd.  It will clean up dead rules and make sure the target chain only contains rules
      * specified here.
      */
-    private void setUidFirewallRules(int chain, SparseIntArray uidRules) {
+    private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
         try {
             int size = uidRules.size();
             int[] uids = new int[size];
diff --git a/com/android/server/notification/ConditionProviders.java b/com/android/server/notification/ConditionProviders.java
index 3444ef3..c0fbfbb 100644
--- a/com/android/server/notification/ConditionProviders.java
+++ b/com/android/server/notification/ConditionProviders.java
@@ -186,6 +186,11 @@
         super.onPackagesChanged(removingPackage, pkgList, uid);
     }
 
+    @Override
+    protected boolean isValidEntry(String packageOrComponent, int userId) {
+        return true;
+    }
+
     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
         synchronized(mMutex) {
             return checkServiceTokenLocked(provider);
diff --git a/com/android/server/notification/ImportanceExtractor.java b/com/android/server/notification/ImportanceExtractor.java
index 46ec92b..452121c 100644
--- a/com/android/server/notification/ImportanceExtractor.java
+++ b/com/android/server/notification/ImportanceExtractor.java
@@ -22,7 +22,7 @@
  * Determines the importance of the given notification.
  */
 public class ImportanceExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantTopicExtractor";
+    private static final String TAG = "ImportanceExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/com/android/server/notification/ManagedServices.java b/com/android/server/notification/ManagedServices.java
index add4184..019c7c2 100644
--- a/com/android/server/notification/ManagedServices.java
+++ b/com/android/server/notification/ManagedServices.java
@@ -45,12 +45,16 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.service.notification.ManagedServiceInfoProto;
+import android.service.notification.ManagedServicesProto;
+import android.service.notification.ManagedServicesProto.ServiceProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
@@ -214,6 +218,53 @@
         }
     }
 
+    public void dump(ProtoOutputStream proto, DumpFilter filter) {
+        proto.write(ManagedServicesProto.CAPTION, getCaption());
+        final int N = mApproved.size();
+        for (int i = 0 ; i < N; i++) {
+            final int userId = mApproved.keyAt(i);
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+            if (approvedByType != null) {
+                final int M = approvedByType.size();
+                for (int j = 0; j < M; j++) {
+                    final boolean isPrimary = approvedByType.keyAt(j);
+                    final ArraySet<String> approved = approvedByType.valueAt(j);
+                    if (approvedByType != null && approvedByType.size() > 0) {
+                        final long sToken = proto.start(ManagedServicesProto.APPROVED);
+                        for (String s : approved) {
+                            proto.write(ServiceProto.NAME, s);
+                        }
+                        proto.write(ServiceProto.USER_ID, userId);
+                        proto.write(ServiceProto.IS_PRIMARY, isPrimary);
+                        proto.end(sToken);
+                    }
+                }
+            }
+        }
+
+        for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+            if (filter != null && !filter.matches(cmpt)) continue;
+
+            final long cToken = proto.start(ManagedServicesProto.ENABLED);
+            cmpt.toProto(proto);
+            proto.end(cToken);
+        }
+
+        for (ManagedServiceInfo info : mServices) {
+            if (filter != null && !filter.matches(info.component)) continue;
+
+            final long lToken = proto.start(ManagedServicesProto.LIVE_SERVICES);
+            info.toProto(proto, this);
+            proto.end(lToken);
+        }
+
+        for (ComponentName name : mSnoozingForCurrentProfiles) {
+            final long cToken = proto.start(ManagedServicesProto.SNOOZED);
+            name.toProto(proto);
+            proto.end(cToken);
+        }
+    }
+
     protected void onSettingRestored(String element, String value, int backupSdkInt, int userId) {
         if (!mUseXml) {
             Slog.d(TAG, "Restored managed service setting: " + element);
@@ -294,6 +345,7 @@
             }
             if (type == XmlPullParser.START_TAG) {
                 if (TAG_MANAGED_SERVICES.equals(tag)) {
+                    Slog.i(TAG, "Read " + mConfig.caption + " permissions from xml");
                     final String approved = XmlUtils.readStringAttribute(parser, ATT_APPROVED_LIST);
                     final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
                     final boolean isPrimary =
@@ -353,6 +405,8 @@
 
     protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
             boolean isPrimary, boolean enabled) {
+        Slog.i(TAG,
+                (enabled ? " Allowing " : "Disallowing ") + mConfig.caption + " " + pkgOrComponent);
         ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.get(userId);
         if (allowedByType == null) {
             allowedByType = new ArrayMap<>();
@@ -460,6 +514,7 @@
     }
 
     public void onUserRemoved(int user) {
+        Slog.i(TAG, "Removing approved services for removed user " + user);
         mApproved.remove(user);
         rebindServices(true);
     }
@@ -491,6 +546,17 @@
         return null;
     }
 
+    protected boolean isServiceTokenValidLocked(IInterface service) {
+        if (service == null) {
+            return false;
+        }
+        ManagedServiceInfo info = getServiceFromTokenLocked(service);
+        if (info != null) {
+            return true;
+        }
+        return false;
+    }
+
     protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
         checkNotNull(service);
         ManagedServiceInfo info = getServiceFromTokenLocked(service);
@@ -543,10 +609,8 @@
         }
 
         // State changed
-        if (DEBUG) {
-            Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " +
-                    component.flattenToShortString());
-        }
+        Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " +
+                component.flattenToShortString());
 
         synchronized (mMutex) {
             final int[] userIds = mUserProfiles.getCurrentProfileIds();
@@ -628,12 +692,10 @@
                 int P = approved.size();
                 for (int k = P - 1; k >= 0; k--) {
                     final String approvedPackageOrComponent = approved.valueAt(k);
-                    if (!hasMatchingServices(approvedPackageOrComponent, userId)){
+                    if (!isValidEntry(approvedPackageOrComponent, userId)){
                         approved.removeAt(k);
-                        if (DEBUG) {
-                            Slog.v(TAG, "Removing " + approvedPackageOrComponent
-                                    + " from approved list; no matching services found");
-                        }
+                        Slog.v(TAG, "Removing " + approvedPackageOrComponent
+                                + " from approved list; no matching services found");
                     } else {
                         if (DEBUG) {
                             Slog.v(TAG, "Keeping " + approvedPackageOrComponent
@@ -678,6 +740,10 @@
         }
     }
 
+    protected boolean isValidEntry(String packageOrComponent, int userId) {
+        return hasMatchingServices(packageOrComponent, userId);
+    }
+
     private boolean hasMatchingServices(String packageOrComponent, int userId) {
         if (!TextUtils.isEmpty(packageOrComponent)) {
             final String packageName = getPackageName(packageOrComponent);
@@ -774,7 +840,12 @@
                     ServiceInfo info = mPm.getServiceInfo(component,
                             PackageManager.MATCH_DIRECT_BOOT_AWARE
                                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
-                    if (info == null || !mConfig.bindPermission.equals(info.permission)) {
+                    if (info == null) {
+                        Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+                                + ": service not found");
+                        continue;
+                    }
+                    if (!mConfig.bindPermission.equals(info.permission)) {
                         Slog.w(TAG, "Not binding " + getCaption() + " service " + component
                                 + ": it does not require the permission " + mConfig.bindPermission);
                         continue;
@@ -830,8 +901,7 @@
             if (name.equals(info.component)
                 && info.userid == userid) {
                 // cut old connections
-                if (DEBUG) Slog.v(TAG, "    disconnecting old " + getCaption() + ": "
-                    + info.service);
+                Slog.v(TAG, "    disconnecting old " + getCaption() + ": " + info.service);
                 removeServiceLocked(i);
                 if (info.connection != null) {
                     mContext.unbindService(info.connection);
@@ -859,7 +929,7 @@
             appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
 
         try {
-            if (DEBUG) Slog.v(TAG, "binding: " + intent);
+            Slog.v(TAG, "binding: " + intent);
             ServiceConnection serviceConnection = new ServiceConnection() {
                 IInterface mService;
 
@@ -917,8 +987,7 @@
         final int N = mServices.size();
         for (int i = N - 1; i >= 0; i--) {
             final ManagedServiceInfo info = mServices.get(i);
-            if (name.equals(info.component)
-                && info.userid == userid) {
+            if (name.equals(info.component) && info.userid == userid) {
                 removeServiceLocked(i);
                 if (info.connection != null) {
                     try {
@@ -945,9 +1014,8 @@
             final int N = mServices.size();
             for (int i = N - 1; i >= 0; i--) {
                 final ManagedServiceInfo info = mServices.get(i);
-                if (info.service.asBinder() == service.asBinder()
-                        && info.userid == userid) {
-                    if (DEBUG) Slog.d(TAG, "Removing active service " + info.component);
+                if (info.service.asBinder() == service.asBinder() && info.userid == userid) {
+                    Slog.d(TAG, "Removing active service " + info.component);
                     serviceInfo = removeServiceLocked(i);
                 }
             }
@@ -1035,6 +1103,16 @@
                     .append(']').toString();
         }
 
+        public void toProto(ProtoOutputStream proto, ManagedServices host) {
+            final long cToken = proto.start(ManagedServiceInfoProto.COMPONENT);
+            component.toProto(proto);
+            proto.end(cToken);
+            proto.write(ManagedServiceInfoProto.USER_ID, userid);
+            proto.write(ManagedServiceInfoProto.SERVICE, service.getClass().getName());
+            proto.write(ManagedServiceInfoProto.IS_SYSTEM, isSystem);
+            proto.write(ManagedServiceInfoProto.IS_GUEST, isGuest(host));
+        }
+
         public boolean enabledAndUserMatches(int nid) {
             if (!isEnabledForCurrentProfiles()) {
                 return false;
diff --git a/com/android/server/notification/NotificationAdjustmentExtractor.java b/com/android/server/notification/NotificationAdjustmentExtractor.java
index 7c82845..3bfd93f 100644
--- a/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -22,7 +22,7 @@
  * Applies adjustments from the group helper and notification assistant
  */
 public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "BadgeExtractor";
+    private static final String TAG = "AdjustmentExtractor";
     private static final boolean DBG = false;
 
 
@@ -35,7 +35,6 @@
             if (DBG) Slog.d(TAG, "skipping empty notification");
             return null;
         }
-
         record.applyAdjustments();
 
         return null;
diff --git a/com/android/server/notification/NotificationChannelExtractor.java b/com/android/server/notification/NotificationChannelExtractor.java
index 46ab556..11c7ab7 100644
--- a/com/android/server/notification/NotificationChannelExtractor.java
+++ b/com/android/server/notification/NotificationChannelExtractor.java
@@ -22,7 +22,7 @@
  * Stores the latest notification channel information for this notification
  */
 public class NotificationChannelExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "BadgeExtractor";
+    private static final String TAG = "ChannelExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/com/android/server/notification/NotificationDelegate.java b/com/android/server/notification/NotificationDelegate.java
index 6a1401c..36bc096 100644
--- a/com/android/server/notification/NotificationDelegate.java
+++ b/com/android/server/notification/NotificationDelegate.java
@@ -16,6 +16,8 @@
 
 package com.android.server.notification;
 
+import android.service.notification.NotificationStats;
+
 import com.android.internal.statusbar.NotificationVisibility;
 
 public interface NotificationDelegate {
@@ -24,7 +26,8 @@
     void onNotificationClick(int callingUid, int callingPid, String key);
     void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex);
     void onNotificationClear(int callingUid, int callingPid,
-            String pkg, String tag, int id, int userId);
+            String pkg, String tag, int id, int userId, String key,
+            @NotificationStats.DismissalSurface int dismissalSurface);
     void onNotificationError(int callingUid, int callingPid,
             String pkg, String tag, int id,
             int uid, int initialPid, String message, int userId);
@@ -35,4 +38,6 @@
             NotificationVisibility[] newlyVisibleKeys,
             NotificationVisibility[] noLongerVisibleKeys);
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
+    void onNotificationDirectReplied(String key);
+    void onNotificationSettingsViewed(String key);
 }
diff --git a/com/android/server/notification/NotificationManagerInternal.java b/com/android/server/notification/NotificationManagerInternal.java
index 4923b06..f1476b3 100644
--- a/com/android/server/notification/NotificationManagerInternal.java
+++ b/com/android/server/notification/NotificationManagerInternal.java
@@ -17,8 +17,10 @@
 package com.android.server.notification;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 
 public interface NotificationManagerInternal {
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
     void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, Notification notification, int userId);
 
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index fe39fcc..14cd055 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -16,8 +16,10 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -125,12 +127,14 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
+import android.service.notification.ListenersDisablingEffectsProto;
 import android.service.notification.NotificationAssistantService;
 import android.service.notification.NotificationListenerService;
 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;
 import android.service.notification.ZenModeConfig;
@@ -675,7 +679,14 @@
 
         @Override
         public void onNotificationClear(int callingUid, int callingPid,
-                String pkg, String tag, int id, int userId) {
+                String pkg, String tag, int id, int userId, String key,
+                @NotificationStats.DismissalSurface int dismissalSurface) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordDismissalSurface(dismissalSurface);
+                }
+            }
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
                     Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
                     true, userId, REASON_CANCEL, null);
@@ -761,12 +772,35 @@
                                 .setType(expanded ? MetricsEvent.TYPE_DETAIL
                                         : MetricsEvent.TYPE_COLLAPSE));
                     }
+                    if (expanded) {
+                        r.recordExpanded();
+                    }
                     EventLogTags.writeNotificationExpansion(key,
                             userAction ? 1 : 0, expanded ? 1 : 0,
                             r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
                 }
             }
         }
+
+        @Override
+        public void onNotificationDirectReplied(String key) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordDirectReplied();
+                }
+            }
+        }
+
+        @Override
+        public void onNotificationSettingsViewed(String key) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordViewedSettings();
+                }
+            }
+        }
     };
 
     @GuardedBy("mNotificationLock")
@@ -1142,6 +1176,12 @@
     }
 
     @VisibleForTesting
+    NotificationRecord getNotificationRecord(String key) {
+        return mNotificationsByKey.get(key);
+    }
+
+
+    @VisibleForTesting
     void setSystemReady(boolean systemReady) {
         mSystemReady = systemReady;
     }
@@ -1216,7 +1256,7 @@
         mUsageStats = usageStats;
         mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
         mRankingHelper = new RankingHelper(getContext(),
-                getContext().getPackageManager(),
+                mPackageManagerClient,
                 mRankingHandler,
                 mUsageStats,
                 extractorNames);
@@ -1269,13 +1309,11 @@
                 R.array.config_notificationFallbackVibePattern,
                 VIBRATE_PATTERN_MAXLEN,
                 DEFAULT_VIBRATE_PATTERN);
-
         mInCallNotificationUri = Uri.parse("file://" +
                 resources.getString(R.string.config_inCallNotificationSound));
         mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
-                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                 .build();
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
@@ -1476,7 +1514,7 @@
                 }
             }
         }
-        mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+        mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
 
         if (!fromListener) {
             final NotificationChannel modifiedChannel =
@@ -3239,14 +3277,51 @@
                 }
             }
             proto.end(records);
-        }
 
-        long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
-        mZenModeHelper.dump(proto);
-        for (ComponentName suppressor : mEffectsSuppressors) {
-            proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+            long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
+            mZenModeHelper.dump(proto);
+            for (ComponentName suppressor : mEffectsSuppressors) {
+                proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+            }
+            proto.end(zenLog);
+
+            long listenersToken = proto.start(NotificationServiceDumpProto.NOTIFICATION_LISTENERS);
+            mListeners.dump(proto, filter);
+            proto.end(listenersToken);
+
+            proto.write(NotificationServiceDumpProto.LISTENER_HINTS, mListenerHints);
+
+            for (int i = 0; i < mListenersDisablingEffects.size(); ++i) {
+                long effectsToken = proto.start(
+                    NotificationServiceDumpProto.LISTENERS_DISABLING_EFFECTS);
+
+                proto.write(
+                    ListenersDisablingEffectsProto.HINT, mListenersDisablingEffects.keyAt(i));
+                final ArraySet<ManagedServiceInfo> listeners =
+                    mListenersDisablingEffects.valueAt(i);
+                for (int j = 0; j < listeners.size(); j++) {
+                    final ManagedServiceInfo listener = listeners.valueAt(i);
+                    listenersToken = proto.start(ListenersDisablingEffectsProto.LISTENERS);
+                    listener.toProto(proto, null);
+                    proto.end(listenersToken);
+                }
+
+                proto.end(effectsToken);
+            }
+
+            long assistantsToken = proto.start(
+                NotificationServiceDumpProto.NOTIFICATION_ASSISTANTS);
+            mAssistants.dump(proto, filter);
+            proto.end(assistantsToken);
+
+            long conditionsToken = proto.start(NotificationServiceDumpProto.CONDITION_PROVIDERS);
+            mConditionProviders.dump(proto, filter);
+            proto.end(conditionsToken);
+
+            long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
+            mRankingHelper.dump(proto, filter);
+            proto.end(rankingToken);
         }
-        proto.end(zenLog);
 
         proto.flush();
     }
@@ -3401,6 +3476,12 @@
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
         @Override
+        public NotificationChannel getNotificationChannel(String pkg, int uid, String
+                channelId) {
+            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+        }
+
+        @Override
         public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
                 String tag, int id, Notification notification, int userId) {
             enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
@@ -3519,6 +3600,21 @@
                 user, null, System.currentTimeMillis());
         final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
 
+        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
+                && (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
+                && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) {
+            // Increase the importance of foreground service notifications unless the user had an
+            // opinion otherwise
+            if (TextUtils.isEmpty(channelId)
+                    || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
+                r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
+            } else {
+                channel.setImportance(IMPORTANCE_LOW);
+                mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+                r.updateNotificationChannel(channel);
+            }
+        }
+
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.sbn.getOverrideGroupKey() != null)) {
             return;
@@ -3752,6 +3848,8 @@
             MetricsLogger.action(r.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
                     .setType(MetricsEvent.TYPE_CLOSE)
+                    .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS,
+                            mDuration)
                     .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
                             mSnoozeCriterionId == null ? 0 : 1));
             boolean wasPosted = removeFromNotificationListsLocked(r);
@@ -3763,6 +3861,7 @@
             } else {
                 mSnoozeHelper.snooze(r, mDuration);
             }
+            r.recordSnoozed();
             savePolicyFile();
         }
     }
@@ -3902,7 +4001,7 @@
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
                         if (old != null && !old.isCanceled) {
                             mListeners.notifyRemovedLocked(n,
-                                    NotificationListenerService.REASON_ERROR);
+                                    NotificationListenerService.REASON_ERROR, null);
                             mHandler.post(new Runnable() {
                                 @Override
                                 public void run() {
@@ -4028,19 +4127,19 @@
             if (mSystemReady && mAudioManager != null) {
                 Uri soundUri = record.getSound();
                 hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
-
                 long[] vibration = record.getVibration();
                 // Demote sound to vibration if vibration missing & phone in vibration mode.
                 if (vibration == null
                         && hasValidSound
                         && (mAudioManager.getRingerModeInternal()
-                        == AudioManager.RINGER_MODE_VIBRATE)) {
+                        == AudioManager.RINGER_MODE_VIBRATE)
+                        && mAudioManager.getStreamVolume(
+                        AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
                     vibration = mFallbackVibrationPattern;
                 }
                 hasValidVibrate = vibration != null;
 
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
-
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
                     if (DBG) Slog.v(TAG, "Interrupting!");
                     if (hasValidSound) {
@@ -4137,8 +4236,9 @@
         boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
         // do not play notifications if there is a user of exclusive audio focus
         // or the device is in vibrate mode
-        if (!mAudioManager.isAudioFocusExclusive() && mAudioManager.getRingerModeInternal()
-                != AudioManager.RINGER_MODE_VIBRATE) {
+        if (!mAudioManager.isAudioFocusExclusive() && (mAudioManager.getRingerModeInternal()
+                != AudioManager.RINGER_MODE_VIBRATE || mAudioManager.getStreamVolume(
+                AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
             final long identity = Binder.clearCallingIdentity();
             try {
                 final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
@@ -4394,6 +4494,7 @@
             ArrayList<String> groupKeyBefore = new ArrayList<>(N);
             ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
             ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
+            ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
             for (int i = 0; i < N; i++) {
                 final NotificationRecord r = mNotificationList.get(i);
                 orderBefore.add(r.getKey());
@@ -4403,6 +4504,7 @@
                 groupKeyBefore.add(r.getGroupKey());
                 overridePeopleBefore.add(r.getPeopleOverride());
                 snoozeCriteriaBefore.add(r.getSnoozeCriteria());
+                userSentimentBefore.add(r.getUserSentiment());
                 mRankingHelper.extractSignals(r);
             }
             mRankingHelper.sort(mNotificationList);
@@ -4414,7 +4516,8 @@
                         || !Objects.equals(channelBefore.get(i), r.getChannel())
                         || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
                         || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
-                        || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())) {
+                        || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
+                        || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())) {
                     mHandler.scheduleSendRankingUpdate();
                     return;
                 }
@@ -4607,6 +4710,10 @@
         // Record caller.
         recordCallerLocked(r);
 
+        if (r.getStats().getDismissalSurface() == NotificationStats.DISMISSAL_NOT_DISMISSED) {
+            r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
+        }
+
         // tell the app
         if (sendDelete) {
             if (r.getNotification().deleteIntent != null) {
@@ -4627,7 +4734,7 @@
                 if (reason != REASON_SNOOZED) {
                     r.isCanceled = true;
                 }
-                mListeners.notifyRemovedLocked(r.sbn, reason);
+                mListeners.notifyRemovedLocked(r.sbn, reason, r.getStats());
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
@@ -5221,6 +5328,7 @@
         Bundle overridePeople = new Bundle();
         Bundle snoozeCriteria = new Bundle();
         Bundle showBadge = new Bundle();
+        Bundle userSentiment = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -5246,6 +5354,7 @@
             overridePeople.putStringArrayList(key, record.getPeopleOverride());
             snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
             showBadge.putBoolean(key, record.canShowBadge());
+            userSentiment.putInt(key, record.getUserSentiment());
         }
         final int M = keys.size();
         String[] keysAr = keys.toArray(new String[M]);
@@ -5256,7 +5365,7 @@
         }
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
                 suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge);
+                channels, overridePeople, snoozeCriteria, showBadge, userSentiment);
     }
 
     boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -5345,7 +5454,7 @@
         @Override
         protected Config getConfig() {
             Config c = new Config();
-            c.caption = "notification assistant service";
+            c.caption = "notification assistant";
             c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
             c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
             c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
@@ -5388,8 +5497,6 @@
                     continue;
                 }
 
-                final int importance = r.getImportance();
-                final boolean fromUser = r.isImportanceFromUser();
                 final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                 mHandler.post(new Runnable() {
                     @Override
@@ -5420,6 +5527,10 @@
                 final String snoozeCriterionId) {
             TrimCache trimCache = new TrimCache(sbn);
             for (final ManagedServiceInfo info : getServices()) {
+                boolean sbnVisible = isVisibleToListener(sbn, info);
+                if (!sbnVisible) {
+                    continue;
+                }
                 final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                 mHandler.post(new Runnable() {
                     @Override
@@ -5541,7 +5652,8 @@
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED);
+                            notifyRemoved(
+                                    info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
                         }
                     });
                     continue;
@@ -5561,7 +5673,8 @@
          * asynchronously notify all listeners about a removed notification
          */
         @GuardedBy("mNotificationLock")
-        public void notifyRemovedLocked(StatusBarNotification sbn, int reason) {
+        public void notifyRemovedLocked(StatusBarNotification sbn, int reason,
+                NotificationStats notificationStats) {
             // make a copy in case changes are made to the underlying Notification object
             // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
             // notification
@@ -5570,11 +5683,14 @@
                 if (!isVisibleToListener(sbn, info)) {
                     continue;
                 }
+                // Only assistants can get stats
+                final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
+                        ? notificationStats : null;
                 final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        notifyRemoved(info, sbnLight, update, reason);
+                        notifyRemoved(info, sbnLight, update, stats, reason);
                     }
                 });
             }
@@ -5679,14 +5795,14 @@
         }
 
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
-                NotificationRankingUpdate rankingUpdate, int reason) {
+                NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
             if (!info.enabledAndUserMatches(sbn.getUserId())) {
                 return;
             }
             final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
-                listener.onNotificationRemoved(sbnHolder, rankingUpdate, reason);
+                listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
             } catch (RemoteException ex) {
                 Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
             }
@@ -5772,10 +5888,9 @@
             final DumpFilter filter = new DumpFilter();
             for (int ai = 0; ai < args.length; ai++) {
                 final String a = args[ai];
-                if ("--proto".equals(args[0])) {
+                if ("--proto".equals(a)) {
                     filter.proto = true;
-                }
-                if ("--noredact".equals(a) || "--reveal".equals(a)) {
+                } else if ("--noredact".equals(a) || "--reveal".equals(a)) {
                     filter.redact = false;
                 } else if ("p".equals(a) || "pkg".equals(a) || "--package".equals(a)) {
                     if (ai < args.length-1) {
@@ -5848,8 +5963,8 @@
 
     private class ShellCmd extends ShellCommand {
         public static final String USAGE = "help\n"
-                + "allow_listener COMPONENT\n"
-                + "disallow_listener COMPONENT\n"
+                + "allow_listener COMPONENT [user_id]\n"
+                + "disallow_listener COMPONENT [user_id]\n"
                 + "set_assistant COMPONENT\n"
                 + "remove_assistant COMPONENT\n"
                 + "allow_dnd PACKAGE\n"
@@ -5880,7 +5995,13 @@
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), true);
+                        }
                     }
                     break;
                     case "disallow_listener": {
@@ -5889,7 +6010,13 @@
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), false);
+                        }
                     }
                     break;
                     case "allow_assistant": {
diff --git a/com/android/server/notification/NotificationRecord.java b/com/android/server/notification/NotificationRecord.java
index 77bf9e3..faa300f 100644
--- a/com/android/server/notification/NotificationRecord.java
+++ b/com/android/server/notification/NotificationRecord.java
@@ -20,6 +20,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEUTRAL;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -41,6 +43,7 @@
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationStats;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
@@ -84,8 +87,6 @@
 
     NotificationUsageStats.SingleNotificationStats stats;
     boolean isCanceled;
-    /** Whether the notification was seen by the user via one of the notification listeners. */
-    boolean mIsSeen;
 
     // These members are used by NotificationSignalExtractors
     // to communicate with the ranking module.
@@ -136,6 +137,8 @@
     private String mChannelIdLogTag;
 
     private final List<Adjustment> mAdjustments;
+    private final NotificationStats mStats;
+    private int mUserSentiment;
 
     @VisibleForTesting
     public NotificationRecord(Context context, StatusBarNotification sbn,
@@ -156,6 +159,7 @@
         mImportance = calculateImportance();
         mLight = calculateLights();
         mAdjustments = new ArrayList<>();
+        mStats = new NotificationStats();
     }
 
     private boolean isPreChannelsNotification() {
@@ -395,7 +399,7 @@
         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
         pw.println(prefix + "pri=" + notification.priority);
         pw.println(prefix + "key=" + sbn.getKey());
-        pw.println(prefix + "seen=" + mIsSeen);
+        pw.println(prefix + "seen=" + mStats.hasSeen());
         pw.println(prefix + "groupKey=" + getGroupKey());
         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
         pw.println(prefix + "contentIntent=" + notification.contentIntent);
@@ -572,6 +576,10 @@
                             adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
                     setOverrideGroupKey(groupOverrideKey);
                 }
+                if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
+                    setUserSentiment(adjustment.getSignals().getInt(
+                            Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
+                }
             }
         }
     }
@@ -740,6 +748,7 @@
                 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank));
         if (visible) {
+            setSeen();
             MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
         }
         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
@@ -777,12 +786,12 @@
 
     /** Check if any of the listeners have marked this notification as seen by the user. */
     public boolean isSeen() {
-        return mIsSeen;
+        return mStats.hasSeen();
     }
 
     /** Mark the notification as seen by the user. */
     public void setSeen() {
-        mIsSeen = true;
+        mStats.setSeen();
     }
 
     public void setAuthoritativeRank(int authoritativeRank) {
@@ -883,6 +892,38 @@
         mSnoozeCriteria = snoozeCriteria;
     }
 
+    private void setUserSentiment(int userSentiment) {
+        mUserSentiment = userSentiment;
+    }
+
+    public int getUserSentiment() {
+        return mUserSentiment;
+    }
+
+    public NotificationStats getStats() {
+        return mStats;
+    }
+
+    public void recordExpanded() {
+        mStats.setExpanded();
+    }
+
+    public void recordDirectReplied() {
+        mStats.setDirectReplied();
+    }
+
+    public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) {
+        mStats.setDismissalSurface(surface);
+    }
+
+    public void recordSnoozed() {
+        mStats.setSnoozed();
+    }
+
+    public void recordViewedSettings() {
+        mStats.setViewedSettings();
+    }
+
     public LogMaker getLogMaker(long now) {
         if (mLogMaker == null) {
             // initialize fields that only change on update (so a new record)
diff --git a/com/android/server/notification/PriorityExtractor.java b/com/android/server/notification/PriorityExtractor.java
index 5d5d39d..7a287db 100644
--- a/com/android/server/notification/PriorityExtractor.java
+++ b/com/android/server/notification/PriorityExtractor.java
@@ -23,7 +23,7 @@
  * Determines if the given notification can bypass Do Not Disturb.
  */
 public class PriorityExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantTopicExtractor";
+    private static final String TAG = "PriorityExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/com/android/server/notification/RankingConfig.java b/com/android/server/notification/RankingConfig.java
index b5ef1c6..b9c0d90 100644
--- a/com/android/server/notification/RankingConfig.java
+++ b/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,7 @@
             int uid, boolean includeDeleted);
     void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp);
-    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
+    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index fc24581..d7e9cf3 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -36,10 +36,13 @@
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -228,7 +231,11 @@
                                 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
                                     NotificationChannel channel = new NotificationChannel(id,
                                             channelName, channelImportance);
-                                    channel.populateFromXml(parser);
+                                    if (forRestore) {
+                                        channel.populateFromXmlForRestore(parser, mContext);
+                                    } else {
+                                        channel.populateFromXml(parser);
+                                    }
                                     r.channels.put(id, channel);
                                 }
                             }
@@ -391,7 +398,11 @@
                     }
 
                     for (NotificationChannel channel : r.channels.values()) {
-                        if (!forBackup || (forBackup && !channel.isDeleted())) {
+                        if (forBackup) {
+                            if (!channel.isDeleted()) {
+                                channel.writeXmlForBackup(out, mContext);
+                            }
+                        } else {
                             channel.writeXml(out);
                         }
                     }
@@ -613,7 +624,8 @@
     }
 
     @Override
-    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) {
+    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
+            boolean fromUser) {
         Preconditions.checkNotNull(updatedChannel);
         Preconditions.checkNotNull(updatedChannel.getId());
         Record r = getOrCreateRecord(pkg, uid);
@@ -627,7 +639,11 @@
         if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
             updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
         }
-        lockFieldsForUpdate(channel, updatedChannel);
+        updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
+        updatedChannel.lockFields(channel.getUserLockedFields());
+        if (fromUser) {
+            lockFieldsForUpdate(channel, updatedChannel);
+        }
         r.channels.put(updatedChannel.getId(), updatedChannel);
 
         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
@@ -874,8 +890,6 @@
 
     @VisibleForTesting
     void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
-        update.unlockFields(update.getUserLockedFields());
-        update.lockFields(original.getUserLockedFields());
         if (original.canBypassDnd() != update.canBypassDnd()) {
             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
         }
@@ -912,8 +926,7 @@
                 pw.print("  ");
                 pw.println(mSignalExtractors[i]);
             }
-        }
-        if (filter == null) {
+
             pw.print(prefix);
             pw.println("per-package config:");
         }
@@ -925,6 +938,52 @@
         dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
     }
 
+    public void dump(ProtoOutputStream proto, NotificationManagerService.DumpFilter filter) {
+        final int N = mSignalExtractors.length;
+        for (int i = 0; i < N; i++) {
+            proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
+                mSignalExtractors[i].getClass().getSimpleName());
+        }
+        synchronized (mRecords) {
+            dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
+        }
+        dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+            mRestoredWithoutUids);
+    }
+
+    private static void dumpRecords(ProtoOutputStream proto, long fieldId,
+            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+        final int N = records.size();
+        long fToken;
+        for (int i = 0; i < N; i++) {
+            final Record r = records.valueAt(i);
+            if (filter == null || filter.matches(r.pkg)) {
+                fToken = proto.start(fieldId);
+
+                proto.write(RecordProto.PACKAGE, r.pkg);
+                proto.write(RecordProto.UID, r.uid);
+                proto.write(RecordProto.IMPORTANCE, r.importance);
+                proto.write(RecordProto.PRIORITY, r.priority);
+                proto.write(RecordProto.VISIBILITY, r.visibility);
+                proto.write(RecordProto.SHOW_BADGE, r.showBadge);
+
+                long token;
+                for (NotificationChannel channel : r.channels.values()) {
+                    token = proto.start(RecordProto.CHANNELS);
+                    channel.toProto(proto);
+                    proto.end(token);
+                }
+                for (NotificationChannelGroup group : r.groups.values()) {
+                    token = proto.start(RecordProto.CHANNEL_GROUPS);
+                    group.toProto(proto);
+                    proto.end(token);
+                }
+
+                proto.end(fToken);
+            }
+        }
+    }
+
     private static void dumpRecords(PrintWriter pw, String prefix,
             NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
         final int N = records.size();
diff --git a/com/android/server/notification/ScheduleCalendar.java b/com/android/server/notification/ScheduleCalendar.java
index 9e8b2e3..40230bd 100644
--- a/com/android/server/notification/ScheduleCalendar.java
+++ b/com/android/server/notification/ScheduleCalendar.java
@@ -42,7 +42,8 @@
 
     public void maybeSetNextAlarm(long now, long nextAlarm) {
         if (mSchedule != null) {
-            if (mSchedule.exitAtAlarm && now > mSchedule.nextAlarm) {
+            if (mSchedule.exitAtAlarm
+                    && (now > mSchedule.nextAlarm || nextAlarm < mSchedule.nextAlarm)) {
                 mSchedule.nextAlarm = nextAlarm;
             }
         }
diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java
index ffdafc5..9fcc67d 100644
--- a/com/android/server/notification/ZenModeHelper.java
+++ b/com/android/server/notification/ZenModeHelper.java
@@ -567,7 +567,7 @@
                     proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, rule.toString());
                 }
             }
-            proto.write(ZenModeProto.POLICY, mConfig.toNotificationPolicy().toString());
+            mConfig.toNotificationPolicy().toProto(proto, ZenModeProto.POLICY);
             proto.write(ZenModeProto.SUPPRESSED_EFFECTS, mSuppressedEffects);
         }
     }
diff --git a/com/android/server/oemlock/OemLockService.java b/com/android/server/oemlock/OemLockService.java
index 40c6639..5b3d1ec 100644
--- a/com/android/server/oemlock/OemLockService.java
+++ b/com/android/server/oemlock/OemLockService.java
@@ -31,6 +31,7 @@
 import android.os.UserManagerInternal;
 import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.service.oemlock.IOemLockService;
+import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Slog;
 
 import com.android.server.LocalServices;
@@ -98,6 +99,7 @@
                         !newRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET);
                 if (!unlockAllowedByAdmin) {
                     mOemLock.setOemUnlockAllowedByDevice(false);
+                    setPersistentDataBlockOemUnlockAllowedBit(false);
                 }
             }
         }
@@ -158,6 +160,7 @@
                 }
 
                 mOemLock.setOemUnlockAllowedByDevice(allowedByUser);
+                setPersistentDataBlockOemUnlockAllowedBit(allowedByUser);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -202,6 +205,20 @@
         }
     };
 
+    /**
+     * Always synchronize the OemUnlockAllowed bit to the FRP partition, which
+     * is used to erase FRP information on a unlockable device.
+     */
+    private void setPersistentDataBlockOemUnlockAllowedBit(boolean allowed) {
+        final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
+                mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+        // if mOemLock is PersistentDataBlockLock, then the bit should have already been set
+        if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)) {
+            Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
+            pdbm.setOemUnlockEnabled(allowed);
+        }
+    }
+
     private boolean isOemUnlockAllowedByAdmin() {
         return !UserManager.get(mContext)
                 .hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM);
diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java
index 415c9a9..6d8cac0 100644
--- a/com/android/server/pm/BackgroundDexOptService.java
+++ b/com/android/server/pm/BackgroundDexOptService.java
@@ -342,8 +342,7 @@
                     DexoptOptions.DEXOPT_BOOT_COMPLETE |
                     (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
             if (is_for_primary_dex) {
-                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg,
-                        PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
                         dexoptFlags));
                 success = result != PackageDexOptimizer.DEX_OPT_FAILED;
                 if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -351,8 +350,7 @@
                 }
             } else {
                 success = pm.performDexOpt(new DexoptOptions(pkg,
-                        PackageManagerService.REASON_BACKGROUND_DEXOPT,
-                        dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
+                        reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
             }
             if (success) {
                 // Dexopt succeeded, remove package from the list of failing ones.
diff --git a/com/android/server/pm/BasePermission.java b/com/android/server/pm/BasePermission.java
deleted file mode 100644
index 30fda1e..0000000
--- a/com/android/server/pm/BasePermission.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2006 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.pm;
-
-import android.content.pm.PackageParser;
-import android.content.pm.PermissionInfo;
-import android.os.UserHandle;
-
-final class BasePermission {
-    final static int TYPE_NORMAL = 0;
-
-    final static int TYPE_BUILTIN = 1;
-
-    final static int TYPE_DYNAMIC = 2;
-
-    final String name;
-
-    String sourcePackage;
-
-    PackageSettingBase packageSetting;
-
-    final int type;
-
-    int protectionLevel;
-
-    PackageParser.Permission perm;
-
-    PermissionInfo pendingInfo;
-
-    /** UID that owns the definition of this permission */
-    int uid;
-
-    /** Additional GIDs given to apps granted this permission */
-    private int[] gids;
-
-    /**
-     * Flag indicating that {@link #gids} should be adjusted based on the
-     * {@link UserHandle} the granted app is running as.
-     */
-    private boolean perUser;
-
-    BasePermission(String _name, String _sourcePackage, int _type) {
-        name = _name;
-        sourcePackage = _sourcePackage;
-        type = _type;
-        // Default to most conservative protection level.
-        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
-    }
-
-    @Override
-    public String toString() {
-        return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
-                + "}";
-    }
-
-    public void setGids(int[] gids, boolean perUser) {
-        this.gids = gids;
-        this.perUser = perUser;
-    }
-
-    public int[] computeGids(int userId) {
-        if (perUser) {
-            final int[] userGids = new int[gids.length];
-            for (int i = 0; i < gids.length; i++) {
-                userGids[i] = UserHandle.getUid(userId, gids[i]);
-            }
-            return userGids;
-        } else {
-            return gids;
-        }
-    }
-
-    public boolean isRuntime() {
-        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                == PermissionInfo.PROTECTION_DANGEROUS;
-    }
-
-    public boolean isDevelopment() {
-        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                == PermissionInfo.PROTECTION_SIGNATURE
-                && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
-    }
-
-    public boolean isInstant() {
-        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
-    }
-
-    public boolean isRuntimeOnly() {
-        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
-    }
-}
diff --git a/com/android/server/pm/DefaultPermissionGrantPolicy.java b/com/android/server/pm/DefaultPermissionGrantPolicy.java
deleted file mode 100644
index a3811ba..0000000
--- a/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ /dev/null
@@ -1,1244 +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.server.pm;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.DownloadManager;
-import android.app.admin.DevicePolicyManager;
-import android.companion.CompanionDeviceManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal.PackagesProvider;
-import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
-import android.content.pm.PackageParser;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.os.storage.StorageManager;
-import android.print.PrintManager;
-import android.provider.CalendarContract;
-import android.provider.ContactsContract;
-import android.provider.MediaStore;
-import android.provider.Telephony.Sms.Intents;
-import android.telephony.TelephonyManager;
-import android.security.Credentials;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-import com.android.internal.util.XmlUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static android.os.Process.FIRST_APPLICATION_UID;
-
-/**
- * This class is the policy for granting runtime permissions to
- * platform components and default handlers in the system such
- * that the device is usable out-of-the-box. For example, the
- * shell UID is a part of the system and the Phone app should
- * have phone related permission by default.
- */
-final class DefaultPermissionGrantPolicy {
-    private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
-    private static final boolean DEBUG = false;
-
-    private static final int DEFAULT_FLAGS =
-            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
-
-    private static final String AUDIO_MIME_TYPE = "audio/mpeg";
-
-    private static final String TAG_EXCEPTIONS = "exceptions";
-    private static final String TAG_EXCEPTION = "exception";
-    private static final String TAG_PERMISSION = "permission";
-    private static final String ATTR_PACKAGE = "package";
-    private static final String ATTR_NAME = "name";
-    private static final String ATTR_FIXED = "fixed";
-
-    private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
-    static {
-        PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
-        PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
-        PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
-        PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
-        PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
-        PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
-        PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
-    }
-
-    private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
-    static {
-        CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
-        CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
-        CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
-    }
-
-    private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
-    static {
-        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
-        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
-    }
-
-    private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
-    static {
-        CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
-        CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
-    }
-
-    private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
-    static {
-        SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
-        SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
-    }
-
-    private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
-    static {
-        MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
-    }
-
-    private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
-    static {
-        CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
-    }
-
-    private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
-    static {
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
-    }
-
-    private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
-    static {
-        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
-        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-    }
-
-    private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
-
-    private static final String ACTION_TRACK = "com.android.fitness.TRACK";
-
-    private final PackageManagerService mService;
-    private final Handler mHandler;
-
-    private PackagesProvider mLocationPackagesProvider;
-    private PackagesProvider mVoiceInteractionPackagesProvider;
-    private PackagesProvider mSmsAppPackagesProvider;
-    private PackagesProvider mDialerAppPackagesProvider;
-    private PackagesProvider mSimCallManagerPackagesProvider;
-    private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
-
-    private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
-
-    public DefaultPermissionGrantPolicy(PackageManagerService service) {
-        mService = service;
-        mHandler = new Handler(mService.mHandlerThread.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
-                    synchronized (mService.mPackages) {
-                        if (mGrantExceptions == null) {
-                            mGrantExceptions = readDefaultPermissionExceptionsLPw();
-                        }
-                    }
-                }
-            }
-        };
-    }
-
-    public void setLocationPackagesProviderLPw(PackagesProvider provider) {
-        mLocationPackagesProvider = provider;
-    }
-
-    public void setVoiceInteractionPackagesProviderLPw(PackagesProvider provider) {
-        mVoiceInteractionPackagesProvider = provider;
-    }
-
-    public void setSmsAppPackagesProviderLPw(PackagesProvider provider) {
-        mSmsAppPackagesProvider = provider;
-    }
-
-    public void setDialerAppPackagesProviderLPw(PackagesProvider provider) {
-        mDialerAppPackagesProvider = provider;
-    }
-
-    public void setSimCallManagerPackagesProviderLPw(PackagesProvider provider) {
-        mSimCallManagerPackagesProvider = provider;
-    }
-
-    public void setSyncAdapterPackagesProviderLPw(SyncAdapterPackagesProvider provider) {
-        mSyncAdapterPackagesProvider = provider;
-    }
-
-    public void grantDefaultPermissions(int userId) {
-        if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
-            grantAllRuntimePermissions(userId);
-        } else {
-            grantPermissionsToSysComponentsAndPrivApps(userId);
-            grantDefaultSystemHandlerPermissions(userId);
-            grantDefaultPermissionExceptions(userId);
-        }
-    }
-
-    private void grantRuntimePermissionsForPackageLocked(int userId, PackageParser.Package pkg) {
-        Set<String> permissions = new ArraySet<>();
-        for (String permission :  pkg.requestedPermissions) {
-            BasePermission bp = mService.mSettings.mPermissions.get(permission);
-            if (bp != null && bp.isRuntime()) {
-                permissions.add(permission);
-            }
-        }
-        if (!permissions.isEmpty()) {
-            grantRuntimePermissionsLPw(pkg, permissions, true, userId);
-        }
-    }
-
-    private void grantAllRuntimePermissions(int userId) {
-        Log.i(TAG, "Granting all runtime permissions for user " + userId);
-        synchronized (mService.mPackages) {
-            for (PackageParser.Package pkg : mService.mPackages.values()) {
-                grantRuntimePermissionsForPackageLocked(userId, pkg);
-            }
-        }
-    }
-
-    public void scheduleReadDefaultPermissionExceptions() {
-        mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
-    }
-
-    private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
-        Log.i(TAG, "Granting permissions to platform components for user " + userId);
-
-        synchronized (mService.mPackages) {
-            for (PackageParser.Package pkg : mService.mPackages.values()) {
-                if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)
-                        || !doesPackageSupportRuntimePermissions(pkg)
-                        || pkg.requestedPermissions.isEmpty()) {
-                    continue;
-                }
-                grantRuntimePermissionsForPackageLocked(userId, pkg);
-            }
-        }
-    }
-
-    private void grantDefaultSystemHandlerPermissions(int userId) {
-        Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
-
-        final PackagesProvider locationPackagesProvider;
-        final PackagesProvider voiceInteractionPackagesProvider;
-        final PackagesProvider smsAppPackagesProvider;
-        final PackagesProvider dialerAppPackagesProvider;
-        final PackagesProvider simCallManagerPackagesProvider;
-        final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
-
-        synchronized (mService.mPackages) {
-            locationPackagesProvider = mLocationPackagesProvider;
-            voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
-            smsAppPackagesProvider = mSmsAppPackagesProvider;
-            dialerAppPackagesProvider = mDialerAppPackagesProvider;
-            simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
-            syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
-        }
-
-        String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
-                ? voiceInteractionPackagesProvider.getPackages(userId) : null;
-        String[] locationPackageNames = (locationPackagesProvider != null)
-                ? locationPackagesProvider.getPackages(userId) : null;
-        String[] smsAppPackageNames = (smsAppPackagesProvider != null)
-                ? smsAppPackagesProvider.getPackages(userId) : null;
-        String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
-                ? dialerAppPackagesProvider.getPackages(userId) : null;
-        String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
-                ? simCallManagerPackagesProvider.getPackages(userId) : null;
-        String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
-                syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
-        String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
-                syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
-
-        synchronized (mService.mPackages) {
-            // Installer
-            PackageParser.Package installerPackage = getSystemPackageLPr(
-                    mService.mRequiredInstallerPackage);
-            if (installerPackage != null
-                    && doesPackageSupportRuntimePermissions(installerPackage)) {
-                grantRuntimePermissionsLPw(installerPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Verifier
-            PackageParser.Package verifierPackage = getSystemPackageLPr(
-                    mService.mRequiredVerifierPackage);
-            if (verifierPackage != null
-                    && doesPackageSupportRuntimePermissions(verifierPackage)) {
-                grantRuntimePermissionsLPw(verifierPackage, STORAGE_PERMISSIONS, true, userId);
-                grantRuntimePermissionsLPw(verifierPackage, PHONE_PERMISSIONS, false, userId);
-                grantRuntimePermissionsLPw(verifierPackage, SMS_PERMISSIONS, false, userId);
-            }
-
-            // SetupWizard
-            PackageParser.Package setupPackage = getSystemPackageLPr(
-                    mService.mSetupWizardPackage);
-            if (setupPackage != null
-                    && doesPackageSupportRuntimePermissions(setupPackage)) {
-                grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, CAMERA_PERMISSIONS, userId);
-            }
-
-            // Camera
-            Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-            PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    cameraIntent, userId);
-            if (cameraPackage != null
-                    && doesPackageSupportRuntimePermissions(cameraPackage)) {
-                grantRuntimePermissionsLPw(cameraPackage, CAMERA_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(cameraPackage, MICROPHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(cameraPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Media provider
-            PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackageLPr(
-                    MediaStore.AUTHORITY, userId);
-            if (mediaStorePackage != null) {
-                grantRuntimePermissionsLPw(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Downloads provider
-            PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackageLPr(
-                    "downloads", userId);
-            if (downloadsPackage != null) {
-                grantRuntimePermissionsLPw(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Downloads UI
-            Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
-            PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    downloadsUiIntent, userId);
-            if (downloadsUiPackage != null
-                    && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
-                grantRuntimePermissionsLPw(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Storage provider
-            PackageParser.Package storagePackage = getDefaultProviderAuthorityPackageLPr(
-                    "com.android.externalstorage.documents", userId);
-            if (storagePackage != null) {
-                grantRuntimePermissionsLPw(storagePackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // CertInstaller
-            Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
-            PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    certInstallerIntent, userId);
-            if (certInstallerPackage != null
-                    && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
-                grantRuntimePermissionsLPw(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Dialer
-            if (dialerAppPackageNames == null) {
-                Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
-                PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        dialerIntent, userId);
-                if (dialerPackage != null) {
-                    grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
-                }
-            } else {
-                for (String dialerAppPackageName : dialerAppPackageNames) {
-                    PackageParser.Package dialerPackage = getSystemPackageLPr(dialerAppPackageName);
-                    if (dialerPackage != null) {
-                        grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
-                    }
-                }
-            }
-
-            // Sim call manager
-            if (simCallManagerPackageNames != null) {
-                for (String simCallManagerPackageName : simCallManagerPackageNames) {
-                    PackageParser.Package simCallManagerPackage =
-                            getSystemPackageLPr(simCallManagerPackageName);
-                    if (simCallManagerPackage != null) {
-                        grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage,
-                                userId);
-                    }
-                }
-            }
-
-            // SMS
-            if (smsAppPackageNames == null) {
-                Intent smsIntent = new Intent(Intent.ACTION_MAIN);
-                smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
-                PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        smsIntent, userId);
-                if (smsPackage != null) {
-                   grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
-                }
-            } else {
-                for (String smsPackageName : smsAppPackageNames) {
-                    PackageParser.Package smsPackage = getSystemPackageLPr(smsPackageName);
-                    if (smsPackage != null) {
-                        grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
-                    }
-                }
-            }
-
-            // Cell Broadcast Receiver
-            Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
-            PackageParser.Package cbrPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(cbrIntent, userId);
-            if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
-                grantRuntimePermissionsLPw(cbrPackage, SMS_PERMISSIONS, userId);
-            }
-
-            // Carrier Provisioning Service
-            Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
-            PackageParser.Package carrierProvPackage =
-                    getDefaultSystemHandlerServicePackageLPr(carrierProvIntent, userId);
-            if (carrierProvPackage != null && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
-                grantRuntimePermissionsLPw(carrierProvPackage, SMS_PERMISSIONS, false, userId);
-            }
-
-            // Calendar
-            Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
-            calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
-            PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    calendarIntent, userId);
-            if (calendarPackage != null
-                    && doesPackageSupportRuntimePermissions(calendarPackage)) {
-                grantRuntimePermissionsLPw(calendarPackage, CALENDAR_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(calendarPackage, CONTACTS_PERMISSIONS, userId);
-            }
-
-            // Calendar provider
-            PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackageLPr(
-                    CalendarContract.AUTHORITY, userId);
-            if (calendarProviderPackage != null) {
-                grantRuntimePermissionsLPw(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(calendarProviderPackage, CALENDAR_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Calendar provider sync adapters
-            List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
-                    calendarSyncAdapterPackages, userId);
-            final int calendarSyncAdapterCount = calendarSyncAdapters.size();
-            for (int i = 0; i < calendarSyncAdapterCount; i++) {
-                PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
-                if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
-                    grantRuntimePermissionsLPw(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
-                }
-            }
-
-            // Contacts
-            Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
-            contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
-            PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    contactsIntent, userId);
-            if (contactsPackage != null
-                    && doesPackageSupportRuntimePermissions(contactsPackage)) {
-                grantRuntimePermissionsLPw(contactsPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(contactsPackage, PHONE_PERMISSIONS, userId);
-            }
-
-            // Contacts provider sync adapters
-            List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
-                    contactsSyncAdapterPackages, userId);
-            final int contactsSyncAdapterCount = contactsSyncAdapters.size();
-            for (int i = 0; i < contactsSyncAdapterCount; i++) {
-                PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
-                if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
-                    grantRuntimePermissionsLPw(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
-                }
-            }
-
-            // Contacts provider
-            PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackageLPr(
-                    ContactsContract.AUTHORITY, userId);
-            if (contactsProviderPackage != null) {
-                grantRuntimePermissionsLPw(contactsProviderPackage, CONTACTS_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(contactsProviderPackage, PHONE_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Device provisioning
-            Intent deviceProvisionIntent = new Intent(
-                    DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
-            PackageParser.Package deviceProvisionPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(deviceProvisionIntent, userId);
-            if (deviceProvisionPackage != null
-                    && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
-                grantRuntimePermissionsLPw(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
-            }
-
-            // Maps
-            Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
-            mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
-            PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    mapsIntent, userId);
-            if (mapsPackage != null
-                    && doesPackageSupportRuntimePermissions(mapsPackage)) {
-                grantRuntimePermissionsLPw(mapsPackage, LOCATION_PERMISSIONS, userId);
-            }
-
-            // Gallery
-            Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
-            galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
-            PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    galleryIntent, userId);
-            if (galleryPackage != null
-                    && doesPackageSupportRuntimePermissions(galleryPackage)) {
-                grantRuntimePermissionsLPw(galleryPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Email
-            Intent emailIntent = new Intent(Intent.ACTION_MAIN);
-            emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
-            PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    emailIntent, userId);
-            if (emailPackage != null
-                    && doesPackageSupportRuntimePermissions(emailPackage)) {
-                grantRuntimePermissionsLPw(emailPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(emailPackage, CALENDAR_PERMISSIONS, userId);
-            }
-
-            // Browser
-            PackageParser.Package browserPackage = null;
-            String defaultBrowserPackage = mService.getDefaultBrowserPackageName(userId);
-            if (defaultBrowserPackage != null) {
-                browserPackage = getPackageLPr(defaultBrowserPackage);
-            }
-            if (browserPackage == null) {
-                Intent browserIntent = new Intent(Intent.ACTION_MAIN);
-                browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
-                browserPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        browserIntent, userId);
-            }
-            if (browserPackage != null
-                    && doesPackageSupportRuntimePermissions(browserPackage)) {
-                grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, userId);
-            }
-
-            // Voice interaction
-            if (voiceInteractPackageNames != null) {
-                for (String voiceInteractPackageName : voiceInteractPackageNames) {
-                    PackageParser.Package voiceInteractPackage = getSystemPackageLPr(
-                            voiceInteractPackageName);
-                    if (voiceInteractPackage != null
-                            && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                CONTACTS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                CALENDAR_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                MICROPHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                PHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                SMS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                LOCATION_PERMISSIONS, userId);
-                    }
-                }
-            }
-
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                // Allow voice search on low-ram devices
-                Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
-                PackageParser.Package globalSearchPickerPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(globalSearchIntent, userId);
-
-                if (globalSearchPickerPackage != null
-                        && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
-                    grantRuntimePermissionsLPw(globalSearchPickerPackage,
-                        MICROPHONE_PERMISSIONS, true, userId);
-                    grantRuntimePermissionsLPw(globalSearchPickerPackage,
-                        LOCATION_PERMISSIONS, true, userId);
-                }
-            }
-
-            // Voice recognition
-            Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
-            voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
-            PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackageLPr(
-                    voiceRecoIntent, userId);
-            if (voiceRecoPackage != null
-                    && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
-                grantRuntimePermissionsLPw(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
-            }
-
-            // Location
-            if (locationPackageNames != null) {
-                for (String packageName : locationPackageNames) {
-                    PackageParser.Package locationPackage = getSystemPackageLPr(packageName);
-                    if (locationPackage != null
-                            && doesPackageSupportRuntimePermissions(locationPackage)) {
-                        grantRuntimePermissionsLPw(locationPackage, CONTACTS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, CALENDAR_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, MICROPHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, PHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, SMS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, LOCATION_PERMISSIONS,
-                                true, userId);
-                        grantRuntimePermissionsLPw(locationPackage, CAMERA_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, SENSORS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, STORAGE_PERMISSIONS, userId);
-                    }
-                }
-            }
-
-            // Music
-            Intent musicIntent = new Intent(Intent.ACTION_VIEW);
-            musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
-            musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
-                    AUDIO_MIME_TYPE);
-            PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    musicIntent, userId);
-            if (musicPackage != null
-                    && doesPackageSupportRuntimePermissions(musicPackage)) {
-                grantRuntimePermissionsLPw(musicPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Home
-            Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-            homeIntent.addCategory(Intent.CATEGORY_HOME);
-            homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
-            PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackageLPr(
-                    homeIntent, userId);
-            if (homePackage != null
-                    && doesPackageSupportRuntimePermissions(homePackage)) {
-                grantRuntimePermissionsLPw(homePackage, LOCATION_PERMISSIONS, false, userId);
-            }
-
-            // Watches
-            if (mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
-                // Home application on watches
-                Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
-                wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
-
-                PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackageLPr(
-                        wearHomeIntent, userId);
-
-                if (wearHomePackage != null
-                        && doesPackageSupportRuntimePermissions(wearHomePackage)) {
-                    grantRuntimePermissionsLPw(wearHomePackage, CONTACTS_PERMISSIONS, false,
-                            userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
-                            userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
-                            userId);
-                }
-
-                // Fitness tracking on watches
-                Intent trackIntent = new Intent(ACTION_TRACK);
-                PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        trackIntent, userId);
-                if (trackPackage != null
-                        && doesPackageSupportRuntimePermissions(trackPackage)) {
-                    grantRuntimePermissionsLPw(trackPackage, SENSORS_PERMISSIONS, false, userId);
-                    grantRuntimePermissionsLPw(trackPackage, LOCATION_PERMISSIONS, false, userId);
-                }
-            }
-
-            // Print Spooler
-            PackageParser.Package printSpoolerPackage = getSystemPackageLPr(
-                    PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
-            if (printSpoolerPackage != null
-                    && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
-                grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
-            }
-
-            // EmergencyInfo
-            Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
-            PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackageLPr(
-                    emergencyInfoIntent, userId);
-            if (emergencyInfoPckg != null
-                    && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
-                grantRuntimePermissionsLPw(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
-                grantRuntimePermissionsLPw(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
-            }
-
-            // NFC Tag viewer
-            Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
-            nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
-            PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackageLPr(
-                    nfcTagIntent, userId);
-            if (nfcTagPkg != null
-                    && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
-                grantRuntimePermissionsLPw(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
-                grantRuntimePermissionsLPw(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
-            }
-
-            // Storage Manager
-            Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
-            PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackageLPr(
-                    storageManagerIntent, userId);
-            if (storageManagerPckg != null
-                    && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
-                grantRuntimePermissionsLPw(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Companion devices
-            PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackageLPr(
-                    CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
-            if (companionDeviceDiscoveryPackage != null
-                    && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
-                grantRuntimePermissionsLPw(companionDeviceDiscoveryPackage,
-                        LOCATION_PERMISSIONS, true, userId);
-            }
-
-            // Ringtone Picker
-            Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
-            PackageParser.Package ringtonePickerPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(ringtonePickerIntent, userId);
-            if (ringtonePickerPackage != null
-                    && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
-                grantRuntimePermissionsLPw(ringtonePickerPackage,
-                        STORAGE_PERMISSIONS, true, userId);
-            }
-
-            mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(
-            PackageParser.Package dialerPackage, int userId) {
-        if (doesPackageSupportRuntimePermissions(dialerPackage)) {
-            boolean isPhonePermFixed =
-                    mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
-            grantRuntimePermissionsLPw(
-                    dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSystemSmsAppLPr(
-            PackageParser.Package smsPackage, int userId) {
-        if (doesPackageSupportRuntimePermissions(smsPackage)) {
-            grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultSmsAppLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package smsPackage = getPackageLPr(packageName);
-        if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
-            grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultDialerAppLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package dialerPackage = getPackageLPr(packageName);
-        if (dialerPackage != null
-                && doesPackageSupportRuntimePermissions(dialerPackage)) {
-            grantRuntimePermissionsLPw(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSimCallManagerLPr(
-            PackageParser.Package simCallManagerPackage, int userId) {
-        Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
-        if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
-            grantRuntimePermissionsLPw(simCallManagerPackage, PHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultSimCallManagerLPr(String packageName, int userId) {
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package simCallManagerPackage = getPackageLPr(packageName);
-        if (simCallManagerPackage != null) {
-            grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToEnabledCarrierAppsLPr(String[] packageNames, int userId) {
-        Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
-        if (packageNames == null) {
-            return;
-        }
-        for (String packageName : packageNames) {
-            PackageParser.Package carrierPackage = getSystemPackageLPr(packageName);
-            if (carrierPackage != null
-                    && doesPackageSupportRuntimePermissions(carrierPackage)) {
-                grantRuntimePermissionsLPw(carrierPackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(carrierPackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(carrierPackage, SMS_PERMISSIONS, userId);
-            }
-        }
-    }
-
-    public void grantDefaultPermissionsToEnabledImsServicesLPr(String[] packageNames, int userId) {
-        Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
-        if (packageNames == null) {
-            return;
-        }
-        for (String packageName : packageNames) {
-            PackageParser.Package imsServicePackage = getSystemPackageLPr(packageName);
-            if (imsServicePackage != null
-                    && doesPackageSupportRuntimePermissions(imsServicePackage)) {
-                grantRuntimePermissionsLPw(imsServicePackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, CAMERA_PERMISSIONS, userId);
-            }
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultBrowserLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default browser for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package browserPackage = getSystemPackageLPr(packageName);
-        if (browserPackage != null
-                && doesPackageSupportRuntimePermissions(browserPackage)) {
-            grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
-        }
-    }
-
-    private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr(
-            Intent intent, int userId) {
-        ResolveInfo handler = mService.resolveIntent(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
-        if (handler == null || handler.activityInfo == null) {
-            return null;
-        }
-        ActivityInfo activityInfo = handler.activityInfo;
-        if (activityInfo.packageName.equals(mService.mResolveActivity.packageName)
-                && activityInfo.name.equals(mService.mResolveActivity.name)) {
-            return null;
-        }
-        return getSystemPackageLPr(handler.activityInfo.packageName);
-    }
-
-    private PackageParser.Package getDefaultSystemHandlerServicePackageLPr(
-            Intent intent, int userId) {
-        List<ResolveInfo> handlers = mService.queryIntentServices(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId)
-                .getList();
-        if (handlers == null) {
-            return null;
-        }
-        final int handlerCount = handlers.size();
-        for (int i = 0; i < handlerCount; i++) {
-            ResolveInfo handler = handlers.get(i);
-            PackageParser.Package handlerPackage = getSystemPackageLPr(
-                    handler.serviceInfo.packageName);
-            if (handlerPackage != null) {
-                return handlerPackage;
-            }
-        }
-        return null;
-    }
-
-    private List<PackageParser.Package> getHeadlessSyncAdapterPackagesLPr(
-            String[] syncAdapterPackageNames, int userId) {
-        List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
-
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
-        for (String syncAdapterPackageName : syncAdapterPackageNames) {
-            homeIntent.setPackage(syncAdapterPackageName);
-
-            ResolveInfo homeActivity = mService.resolveIntent(homeIntent,
-                    homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS,
-                    userId);
-            if (homeActivity != null) {
-                continue;
-            }
-
-            PackageParser.Package syncAdapterPackage = getSystemPackageLPr(syncAdapterPackageName);
-            if (syncAdapterPackage != null) {
-                syncAdapterPackages.add(syncAdapterPackage);
-            }
-        }
-
-        return syncAdapterPackages;
-    }
-
-    private PackageParser.Package getDefaultProviderAuthorityPackageLPr(
-            String authority, int userId) {
-        ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
-        if (provider != null) {
-            return getSystemPackageLPr(provider.packageName);
-        }
-        return null;
-    }
-
-    private PackageParser.Package getPackageLPr(String packageName) {
-        return mService.mPackages.get(packageName);
-    }
-
-    private PackageParser.Package getSystemPackageLPr(String packageName) {
-        PackageParser.Package pkg = getPackageLPr(packageName);
-        if (pkg != null && pkg.isSystemApp()) {
-            return !isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg) ? pkg : null;
-        }
-        return null;
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            int userId) {
-        grantRuntimePermissionsLPw(pkg, permissions, false, false, userId);
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, int userId) {
-        grantRuntimePermissionsLPw(pkg, permissions, systemFixed, false, userId);
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
-        if (pkg.requestedPermissions.isEmpty()) {
-            return;
-        }
-
-        List<String> requestedPermissions = pkg.requestedPermissions;
-        Set<String> grantablePermissions = null;
-
-        // If this is the default Phone or SMS app we grant permissions regardless
-        // whether the version on the system image declares the permission as used since
-        // selecting the app as the default Phone or SMS the user makes a deliberate
-        // choice to grant this app the permissions needed to function. For all other
-        // apps, (default grants on first boot and user creation) we don't grant default
-        // permissions if the version on the system image does not declare them.
-        if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
-            PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-            if (sysPs != null && sysPs.pkg != null) {
-                if (sysPs.pkg.requestedPermissions.isEmpty()) {
-                    return;
-                }
-                if (!requestedPermissions.equals(sysPs.pkg.requestedPermissions)) {
-                    grantablePermissions = new ArraySet<>(requestedPermissions);
-                    requestedPermissions = sysPs.pkg.requestedPermissions;
-                }
-            }
-        }
-
-        final int grantablePermissionCount = requestedPermissions.size();
-        for (int i = 0; i < grantablePermissionCount; i++) {
-            String permission = requestedPermissions.get(i);
-
-            // If there is a disabled system app it may request a permission the updated
-            // version ot the data partition doesn't, In this case skip the permission.
-            if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
-                continue;
-            }
-
-            if (permissions.contains(permission)) {
-                final int flags = mService.getPermissionFlags(permission, pkg.packageName, userId);
-
-                // If any flags are set to the permission, then it is either set in
-                // its current state by the system or device/profile owner or the user.
-                // In all these cases we do not want to clobber the current state.
-                // Unless the caller wants to override user choices. The override is
-                // to make sure we can grant the needed permission to the default
-                // sms and phone apps after the user chooses this in the UI.
-                if (flags == 0 || isDefaultPhoneOrSms) {
-                    // Never clobber policy or system.
-                    final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                            | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-                    if ((flags & fixedFlags) != 0) {
-                        continue;
-                    }
-
-                    mService.grantRuntimePermission(pkg.packageName, permission, userId);
-                    if (DEBUG) {
-                        Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
-                                + permission + " to default handler " + pkg.packageName);
-                    }
-
-                    int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-                    if (systemFixed) {
-                        newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-                    }
-
-                    mService.updatePermissionFlags(permission, pkg.packageName,
-                            newFlags, newFlags, userId);
-                }
-
-                // If a component gets a permission for being the default handler A
-                // and also default handler B, we grant the weaker grant form.
-                if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
-                        && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
-                        && !systemFixed) {
-                    if (DEBUG) {
-                        Log.i(TAG, "Granted not fixed " + permission + " to default handler "
-                                + pkg.packageName);
-                    }
-                    mService.updatePermissionFlags(permission, pkg.packageName,
-                            PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
-                }
-            }
-        }
-    }
-
-    private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {
-        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
-            return true;
-        }
-        if (!pkg.isPrivilegedApp()) {
-            return false;
-        }
-        PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-        if (sysPkg != null && sysPkg.pkg != null) {
-            if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
-                return false;
-            }
-        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
-            return false;
-        }
-        return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
-                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
-    }
-
-    private void grantDefaultPermissionExceptions(int userId) {
-        synchronized (mService.mPackages) {
-            mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
-
-            if (mGrantExceptions == null) {
-                mGrantExceptions = readDefaultPermissionExceptionsLPw();
-            }
-
-            // mGrantExceptions is null only before the first read and then
-            // it serves as a cache of the default grants that should be
-            // performed for every user. If there is an entry then the app
-            // is on the system image and supports runtime permissions.
-            Set<String> permissions = null;
-            final int exceptionCount = mGrantExceptions.size();
-            for (int i = 0; i < exceptionCount; i++) {
-                String packageName = mGrantExceptions.keyAt(i);
-                PackageParser.Package pkg = getSystemPackageLPr(packageName);
-                List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
-                final int permissionGrantCount = permissionGrants.size();
-                for (int j = 0; j < permissionGrantCount; j++) {
-                    DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
-                    if (permissions == null) {
-                        permissions = new ArraySet<>();
-                    } else {
-                        permissions.clear();
-                    }
-                    permissions.add(permissionGrant.name);
-                    grantRuntimePermissionsLPw(pkg, permissions,
-                            permissionGrant.fixed, userId);
-                }
-            }
-        }
-    }
-
-    private File[] getDefaultPermissionFiles() {
-        ArrayList<File> ret = new ArrayList<File>();
-        File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
-        if (dir.isDirectory() && dir.canRead()) {
-            Collections.addAll(ret, dir.listFiles());
-        }
-        dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
-        if (dir.isDirectory() && dir.canRead()) {
-            Collections.addAll(ret, dir.listFiles());
-        }
-        return ret.isEmpty() ? null : ret.toArray(new File[0]);
-    }
-
-    private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
-            readDefaultPermissionExceptionsLPw() {
-        File[] files = getDefaultPermissionFiles();
-        if (files == null) {
-            return new ArrayMap<>(0);
-        }
-
-        ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
-
-        // Iterate over the files in the directory and scan .xml files
-        for (File file : files) {
-            if (!file.getPath().endsWith(".xml")) {
-                Slog.i(TAG, "Non-xml file " + file + " in " + file.getParent() + " directory, ignoring");
-                continue;
-            }
-            if (!file.canRead()) {
-                Slog.w(TAG, "Default permissions file " + file + " cannot be read");
-                continue;
-            }
-            try (
-                InputStream str = new BufferedInputStream(new FileInputStream(file))
-            ) {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(str, null);
-                parse(parser, grantExceptions);
-            } catch (XmlPullParserException | IOException e) {
-                Slog.w(TAG, "Error reading default permissions file " + file, e);
-            }
-        }
-
-        return grantExceptions;
-    }
-
-    private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
-            outGrantExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            if (TAG_EXCEPTIONS.equals(parser.getName())) {
-                parseExceptions(parser, outGrantExceptions);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName());
-            }
-        }
-    }
-
-    private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
-            outGrantExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            if (TAG_EXCEPTION.equals(parser.getName())) {
-                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-
-                List<DefaultPermissionGrant> packageExceptions =
-                        outGrantExceptions.get(packageName);
-                if (packageExceptions == null) {
-                    // The package must be on the system image
-                    PackageParser.Package pkg = getSystemPackageLPr(packageName);
-                    if (pkg == null) {
-                        Log.w(TAG, "Unknown package:" + packageName);
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-
-                    // The package must support runtime permissions
-                    if (!doesPackageSupportRuntimePermissions(pkg)) {
-                        Log.w(TAG, "Skipping non supporting runtime permissions package:"
-                                + packageName);
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-                    packageExceptions = new ArrayList<>();
-                    outGrantExceptions.put(packageName, packageExceptions);
-                }
-
-                parsePermission(parser, packageExceptions);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
-            }
-        }
-    }
-
-    private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
-            outPackageExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            if (TAG_PERMISSION.contains(parser.getName())) {
-                String name = parser.getAttributeValue(null, ATTR_NAME);
-                if (name == null) {
-                    Log.w(TAG, "Mandatory name attribute missing for permission tag");
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-                }
-
-                final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
-
-                DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
-                outPackageExceptions.add(exception);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
-            }
-        }
-    }
-
-    private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
-        return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
-    }
-
-    private static final class DefaultPermissionGrant {
-        final String name;
-        final boolean fixed;
-
-        public DefaultPermissionGrant(String name, boolean fixed) {
-            this.name = name;
-            this.fixed = fixed;
-        }
-    }
-}
diff --git a/com/android/server/pm/DumpState.java b/com/android/server/pm/DumpState.java
new file mode 100644
index 0000000..7ebef83
--- /dev/null
+++ b/com/android/server/pm/DumpState.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.server.pm;
+
+public final class DumpState {
+    public static final int DUMP_LIBS = 1 << 0;
+    public static final int DUMP_FEATURES = 1 << 1;
+    public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
+    public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
+    public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
+    public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
+    public static final int DUMP_PERMISSIONS = 1 << 6;
+    public static final int DUMP_PACKAGES = 1 << 7;
+    public static final int DUMP_SHARED_USERS = 1 << 8;
+    public static final int DUMP_MESSAGES = 1 << 9;
+    public static final int DUMP_PROVIDERS = 1 << 10;
+    public static final int DUMP_VERIFIERS = 1 << 11;
+    public static final int DUMP_PREFERRED = 1 << 12;
+    public static final int DUMP_PREFERRED_XML = 1 << 13;
+    public static final int DUMP_KEYSETS = 1 << 14;
+    public static final int DUMP_VERSION = 1 << 15;
+    public static final int DUMP_INSTALLS = 1 << 16;
+    public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+    public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
+    public static final int DUMP_FROZEN = 1 << 19;
+    public static final int DUMP_DEXOPT = 1 << 20;
+    public static final int DUMP_COMPILER_STATS = 1 << 21;
+    public static final int DUMP_CHANGES = 1 << 22;
+    public static final int DUMP_VOLUMES = 1 << 23;
+
+    public static final int OPTION_SHOW_FILTERS = 1 << 0;
+
+    private int mTypes;
+
+    private int mOptions;
+
+    private boolean mTitlePrinted;
+
+    private SharedUserSetting mSharedUser;
+
+    public boolean isDumping(int type) {
+        if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
+            return true;
+        }
+
+        return (mTypes & type) != 0;
+    }
+
+    public void setDump(int type) {
+        mTypes |= type;
+    }
+
+    public boolean isOptionEnabled(int option) {
+        return (mOptions & option) != 0;
+    }
+
+    public void setOptionEnabled(int option) {
+        mOptions |= option;
+    }
+
+    public boolean onTitlePrinted() {
+        final boolean printed = mTitlePrinted;
+        mTitlePrinted = true;
+        return printed;
+    }
+
+    public boolean getTitlePrinted() {
+        return mTitlePrinted;
+    }
+
+    public void setTitlePrinted(boolean enabled) {
+        mTitlePrinted = enabled;
+    }
+
+    public SharedUserSetting getSharedUser() {
+        return mSharedUser;
+    }
+
+    public void setSharedUser(SharedUserSetting user) {
+        mSharedUser = user;
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/pm/InstantAppRegistry.java b/com/android/server/pm/InstantAppRegistry.java
index e1e5b35..c964f91 100644
--- a/com/android/server/pm/InstantAppRegistry.java
+++ b/com/android/server/pm/InstantAppRegistry.java
@@ -49,6 +49,8 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.permission.BasePermission;
+
 import libcore.io.IoUtils;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -878,8 +880,9 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             for (String grantedPermission : appInfo.getGrantedPermissions()) {
-                BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
-                if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
+                final boolean propagatePermission =
+                        mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
+                if (propagatePermission) {
                     mService.grantRuntimePermission(packageName, grantedPermission, userId);
                 }
             }
diff --git a/com/android/server/pm/KeySetManagerService.java b/com/android/server/pm/KeySetManagerService.java
index 49d3c8b..3574466 100644
--- a/com/android/server/pm/KeySetManagerService.java
+++ b/com/android/server/pm/KeySetManagerService.java
@@ -565,7 +565,7 @@
     }
 
     public void dumpLPr(PrintWriter pw, String packageName,
-                        PackageManagerService.DumpState dumpState) {
+                        DumpState dumpState) {
         boolean printedHeader = false;
         for (ArrayMap.Entry<String, PackageSetting> e : mPackages.entrySet()) {
             String keySetPackage = e.getKey();
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 0f580d8..8ebeeae 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -23,6 +23,7 @@
 import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Log;
@@ -103,7 +104,17 @@
     }
 
     static boolean canOptimizePackage(PackageParser.Package pkg) {
-        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+        // We do not dexopt a package with no code.
+        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+            return false;
+        }
+
+        // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+        if (pkg.isPrivilegedApp()) {
+            return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+        }
+
+        return true;
     }
 
     /**
@@ -354,18 +365,13 @@
                 + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
                 + " target-filter=" + compilerFilter);
 
-        String classLoaderContext;
-        if (dexUseInfo.isUnknownClassLoaderContext() ||
-                dexUseInfo.isUnsupportedClassLoaderContext() ||
-                dexUseInfo.isVariableClassLoaderContext()) {
-            // If we have an unknown (not yet set), unsupported (custom class loaders), or a
-            // variable class loader chain, compile without a context and mark the oat file with
-            // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation.
-            // TODO(calin): We should just extract in this case.
-            classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
-        } else {
-            classLoaderContext = dexUseInfo.getClassLoaderContext();
-        }
+        // TODO(calin): b/64530081 b/66984396. Use SKIP_SHARED_LIBRARY_CHECK for the context
+        // (instead of dexUseInfo.getClassLoaderContext()) in order to compile secondary dex files
+        // in isolation (and avoid to extract/verify the main apk if it's in the class path).
+        // Note this trades correctness for performance since the resulting slow down is
+        // unacceptable in some cases until b/64530081 is fixed.
+        String classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
+
         try {
             for (String isa : dexUseInfo.getLoaderIsas()) {
                 // Reuse the same dexopt path as for the primary apks. We don't need all the
@@ -425,7 +431,7 @@
             }
 
             if (useInfo.isUsedByOtherApps(path)) {
-                pw.println("used be other apps: " + useInfo.getLoadingPackages(path));
+                pw.println("used by other apps: " + useInfo.getLoadingPackages(path));
             }
 
             Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap();
@@ -438,19 +444,10 @@
                     PackageDexUsage.DexUseInfo dexUseInfo = e.getValue();
                     pw.println(dex);
                     pw.increaseIndent();
-                    for (String isa : dexUseInfo.getLoaderIsas()) {
-                        String status = null;
-                        try {
-                            status = DexFile.getDexFileStatus(path, isa);
-                        } catch (IOException ioe) {
-                             status = "[Exception]: " + ioe.getMessage();
-                        }
-                        pw.println(isa + ": " + status);
-                    }
-
+                    // TODO(calin): get the status of the oat file (needs installd call)
                     pw.println("class loader context: " + dexUseInfo.getClassLoaderContext());
                     if (dexUseInfo.isUsedByOtherApps()) {
-                        pw.println("used be other apps: " + dexUseInfo.getLoadingPackages());
+                        pw.println("used by other apps: " + dexUseInfo.getLoadingPackages());
                     }
                     pw.decreaseIndent();
                 }
@@ -474,8 +471,9 @@
         }
 
         if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
-            // If the dex files is used by other apps, we cannot use profile-guided compilation.
-            return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
+            // If the dex files is used by other apps, apply the shared filter.
+            return PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+                    PackageManagerService.REASON_SHARED);
         }
 
         return targetCompilerFilter;
diff --git a/com/android/server/pm/PackageInstallerService.java b/com/android/server/pm/PackageInstallerService.java
index 1fa37b9..09f9cb8 100644
--- a/com/android/server/pm/PackageInstallerService.java
+++ b/com/android/server/pm/PackageInstallerService.java
@@ -80,6 +80,8 @@
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -122,6 +124,7 @@
 
     private final Context mContext;
     private final PackageManagerService mPm;
+    private final PermissionManagerInternal mPermissionManager;
 
     private AppOpsManager mAppOps;
 
@@ -177,6 +180,7 @@
     public PackageInstallerService(Context context, PackageManagerService pm) {
         mContext = context;
         mPm = pm;
+        mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
 
         mInstallThread = new HandlerThread(TAG);
         mInstallThread.start();
@@ -243,35 +247,6 @@
         }
     }
 
-    public void onSecureContainersAvailable() {
-        synchronized (mSessions) {
-            final ArraySet<String> unclaimed = new ArraySet<>();
-            for (String cid : PackageHelper.getSecureContainerList()) {
-                if (isStageName(cid)) {
-                    unclaimed.add(cid);
-                }
-            }
-
-            // Ignore stages claimed by active sessions
-            for (int i = 0; i < mSessions.size(); i++) {
-                final PackageInstallerSession session = mSessions.valueAt(i);
-                final String cid = session.stageCid;
-
-                if (unclaimed.remove(cid)) {
-                    // Claimed by active session, mount it
-                    PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
-                            Process.SYSTEM_UID);
-                }
-            }
-
-            // Clean up orphaned staging containers
-            for (String cid : unclaimed) {
-                Slog.w(TAG, "Deleting orphan container " + cid);
-                PackageHelper.destroySdDir(cid);
-            }
-        }
-    }
-
     public static boolean isStageName(String name) {
         final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
         final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
@@ -426,7 +401,8 @@
     private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
             throws IOException {
         final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
+        mPermissionManager.enforceCrossUserPermission(
+                callingUid, userId, true, true, "createSession");
 
         if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
             throw new SecurityException("User restriction prevents installing");
@@ -671,13 +647,6 @@
         return "smdl" + sessionId + ".tmp";
     }
 
-    static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException {
-        if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(),
-                Process.SYSTEM_UID, true) == null) {
-            throw new IOException("Failed to create session cid: " + stageCid);
-        }
-    }
-
     @Override
     public SessionInfo getSessionInfo(int sessionId) {
         synchronized (mSessions) {
@@ -688,7 +657,8 @@
 
     @Override
     public ParceledListSlice<SessionInfo> getAllSessions(int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getAllSessions");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "getAllSessions");
 
         final List<SessionInfo> result = new ArrayList<>();
         synchronized (mSessions) {
@@ -704,7 +674,8 @@
 
     @Override
     public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getMySessions");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "getMySessions");
         mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
 
         final List<SessionInfo> result = new ArrayList<>();
@@ -726,7 +697,7 @@
     public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
                 IntentSender statusReceiver, int userId) throws RemoteException {
         final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
         if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
             mAppOps.checkPackage(callingUid, callerPackageName);
         }
@@ -775,7 +746,8 @@
 
     @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "registerCallback");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "registerCallback");
         mCallbacks.register(callback, userId);
     }
 
diff --git a/com/android/server/pm/PackageInstallerSession.java b/com/android/server/pm/PackageInstallerSession.java
index ff6e5b3..d62f093 100644
--- a/com/android/server/pm/PackageInstallerSession.java
+++ b/com/android/server/pm/PackageInstallerSession.java
@@ -36,7 +36,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.internal.util.XmlUtils.writeUriAttribute;
-import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 
 import android.Manifest;
@@ -481,12 +480,7 @@
             if (stageDir != null) {
                 mResolvedStageDir = stageDir;
             } else {
-                final String path = PackageHelper.getSdDir(stageCid);
-                if (path != null) {
-                    mResolvedStageDir = new File(path);
-                } else {
-                    throw new IOException("Failed to resolve path to container " + stageCid);
-                }
+                throw new IOException("Missing stageDir");
             }
         }
         return mResolvedStageDir;
@@ -880,14 +874,6 @@
             return;
         }
 
-        if (stageCid != null) {
-            // Figure out the final installed size and resize the container once
-            // and for all. Internally the parser handles straddling between two
-            // locations when inheriting.
-            final long finalSize = calculateInstalledSize();
-            resizeContainer(stageCid, finalSize);
-        }
-
         // Inherit any packages and native libraries from existing install that
         // haven't been overridden.
         if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
@@ -924,11 +910,6 @@
         // Unpack native libraries
         extractNativeLibraries(mResolvedStageDir, params.abiOverride);
 
-        // Container is ready to go, let's seal it up!
-        if (stageCid != null) {
-            finalizeAndFixContainer(stageCid);
-        }
-
         // We've reached point of no return; call into PMS to install the stage.
         // Regardless of success or failure we always destroy session.
         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@@ -953,7 +934,7 @@
         }
 
         mRelinquished = true;
-        mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
+        mPm.installStage(mPackageName, stageDir, localObserver, params,
                 mInstallerPackageName, mInstallerUid, user, mCertificates);
     }
 
@@ -1212,11 +1193,9 @@
         // straddled between the inherited and staged APKs.
         final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
                 splitPaths.toArray(new String[splitPaths.size()]), null);
-        final boolean isForwardLocked =
-                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
 
         try {
-            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
+            return PackageHelper.calculateInstalledSize(pkg, params.abiOverride);
         } catch (IOException e) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Failed to calculate install size", e);
@@ -1345,52 +1324,6 @@
         }
     }
 
-    private static void resizeContainer(String cid, long targetSize)
-            throws PackageManagerException {
-        String path = PackageHelper.getSdDir(cid);
-        if (path == null) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to find mounted " + cid);
-        }
-
-        final long currentSize = new File(path).getTotalSpace();
-        if (currentSize > targetSize) {
-            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
-                    + targetSize + "; skipping resize");
-            return;
-        }
-
-        if (!PackageHelper.unMountSdDir(cid)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to unmount " + cid + " before resize");
-        }
-
-        if (!PackageHelper.resizeSdDir(targetSize, cid,
-                PackageManagerService.getEncryptKey())) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to resize " + cid + " to " + targetSize + " bytes");
-        }
-
-        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
-                Process.SYSTEM_UID, false);
-        if (path == null) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to mount " + cid + " after resize");
-        }
-    }
-
-    private void finalizeAndFixContainer(String cid) throws PackageManagerException {
-        if (!PackageHelper.finalizeSdDir(cid)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to finalize container " + cid);
-        }
-
-        if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to fix permissions on container " + cid);
-        }
-    }
-
     void setPermissionsResult(boolean accepted) {
         if (!mSealed) {
             throw new SecurityException("Must be sealed to accept permissions");
@@ -1419,20 +1352,8 @@
             if (!mPrepared) {
                 if (stageDir != null) {
                     prepareStageDir(stageDir);
-                } else if (stageCid != null) {
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        prepareExternalStageCid(stageCid, params.sizeBytes);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-
-                    // TODO: deliver more granular progress for ASEC allocation
-                    mInternalProgress = 0.25f;
-                    computeProgressLocked(true);
                 } else {
-                    throw new IllegalArgumentException(
-                            "Exactly one of stageDir or stageCid stage must be set");
+                    throw new IllegalArgumentException("stageDir must be set");
                 }
 
                 mPrepared = true;
@@ -1534,9 +1455,6 @@
             } catch (InstallerException ignored) {
             }
         }
-        if (stageCid != null) {
-            PackageHelper.destroySdDir(stageCid);
-        }
     }
 
     void dump(IndentingPrintWriter pw) {
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index ff52e0e..7d1a647 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -55,7 +55,6 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
 import static android.content.pm.PackageManager.INSTALL_INTERNAL;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
@@ -103,10 +102,9 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
-
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
 import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
 
 import android.Manifest;
@@ -160,10 +158,11 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageStats;
@@ -230,7 +229,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Base64;
-import android.util.TimingsTraceLog;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
@@ -244,6 +242,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import android.util.TimingsTraceLog;
 import android.util.Xml;
 import android.util.jar.StrictJarFile;
 import android.util.proto.ProtoOutputStream;
@@ -283,12 +282,19 @@
 import com.android.server.Watchdog;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PermissionsState.PermissionState;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.pm.permission.PermissionManagerInternal;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import dalvik.system.CloseGuard;
@@ -338,6 +344,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -385,7 +392,7 @@
 public class PackageManagerService extends IPackageManager.Stub
         implements PackageSender {
     static final String TAG = "PackageManager";
-    static final boolean DEBUG_SETTINGS = false;
+    public static final boolean DEBUG_SETTINGS = false;
     static final boolean DEBUG_PREFERRED = false;
     static final boolean DEBUG_UPGRADE = false;
     static final boolean DEBUG_DOMAIN_VERIFICATION = false;
@@ -396,7 +403,7 @@
     private static final boolean DEBUG_SHOW_INFO = false;
     private static final boolean DEBUG_PACKAGE_INFO = false;
     private static final boolean DEBUG_INTENT_MATCHING = false;
-    private static final boolean DEBUG_PACKAGE_SCANNING = false;
+    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;
@@ -427,9 +434,6 @@
     private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
     private static final int SHELL_UID = Process.SHELL_UID;
 
-    // Cap the size of permission trees that 3rd party apps can define
-    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;     // characters of text
-
     // Suffix used during package installation when copying/moving
     // package apks to install directory.
     private static final String INSTALL_PACKAGE_SUFFIX = "-";
@@ -578,8 +582,9 @@
     public static final int REASON_BACKGROUND_DEXOPT = 3;
     public static final int REASON_AB_OTA = 4;
     public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 5;
+    public static final int REASON_SHARED = 6;
 
-    public static final int REASON_LAST = REASON_INACTIVE_PACKAGE_DOWNGRADE;
+    public static final int REASON_LAST = REASON_SHARED;
 
     /** All dangerous permission names in the same order as the events in MetricsEvent */
     private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
@@ -654,9 +659,6 @@
     @GuardedBy("mPackages")
     private boolean mDexOptDialogShown;
 
-    /** The location for ASEC container files on internal storage. */
-    final String mAsecInternalPath;
-
     // Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
     // LOCK HELD.  Can be called with mInstallLock held.
     @GuardedBy("mInstallLock")
@@ -862,7 +864,7 @@
                 String targetPath) {
             return getStaticOverlayPaths(targetPackageName, targetPath);
         }
-    };
+    }
 
     class ParallelPackageParserCallback extends PackageParserCallback {
         List<PackageParser.Package> mOverlayPackages = null;
@@ -1004,7 +1006,9 @@
     final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
             = new SparseArray<IntentFilterVerificationState>();
 
+    // TODO remove this and go through mPermissonManager directly
     final DefaultPermissionGrantPolicy mDefaultPermissionPolicy;
+    private final PermissionManagerInternal mPermissionManager;
 
     // List of packages names to keep cached, even if they are uninstalled for all users
     private List<String> mKeepUninstalledPackages;
@@ -1315,7 +1319,6 @@
     static final int POST_INSTALL = 9;
     static final int MCS_RECONNECT = 10;
     static final int MCS_GIVE_UP = 11;
-    static final int UPDATED_MEDIA_STATUS = 12;
     static final int WRITE_SETTINGS = 13;
     static final int WRITE_PACKAGE_RESTRICTIONS = 14;
     static final int PACKAGE_VERIFIED = 15;
@@ -1714,32 +1717,6 @@
 
                     Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);
                 } break;
-                case UPDATED_MEDIA_STATUS: {
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "Got message UPDATED_MEDIA_STATUS");
-                    boolean reportStatus = msg.arg1 == 1;
-                    boolean doGc = msg.arg2 == 1;
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "reportStatus=" + reportStatus + ", doGc = " + doGc);
-                    if (doGc) {
-                        // Force a gc to clear up stale containers.
-                        Runtime.getRuntime().gc();
-                    }
-                    if (msg.obj != null) {
-                        @SuppressWarnings("unchecked")
-                        Set<AsecInstallArgs> args = (Set<AsecInstallArgs>) msg.obj;
-                        if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading all containers");
-                        // Unload containers
-                        unloadAllContainers(args);
-                    }
-                    if (reportStatus) {
-                        try {
-                            if (DEBUG_SD_INSTALL) Log.i(TAG,
-                                    "Invoking StorageManagerService call back");
-                            PackageHelper.getStorageManager().finishMediaUpdate();
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "StorageManagerService not running?");
-                        }
-                    }
-                } break;
                 case WRITE_SETTINGS: {
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mPackages) {
@@ -1910,6 +1887,69 @@
         }
     }
 
+    private PermissionCallback mPermissionCallback = new PermissionCallback() {
+        @Override
+        public void onGidsChanged(int appId, int userId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
+                }
+            });
+        }
+        @Override
+        public void onPermissionGranted(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            // Not critical; if this is lost, the application has to request again.
+            synchronized (mPackages) {
+                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+            }
+        }
+        @Override
+        public void onInstallPermissionGranted() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionRevoked(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            synchronized (mPackages) {
+                // Critical; after this call the application should never have the permission
+                mSettings.writeRuntimePermissionsForUserLPr(userId, true);
+            }
+
+            final int appId = UserHandle.getAppId(uid);
+            killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+        }
+        @Override
+        public void onInstallPermissionRevoked() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionUpdated(int userId) {
+            synchronized (mPackages) {
+                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+            }
+        }
+        @Override
+        public void onInstallPermissionUpdated() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionRemoved() {
+            synchronized (mPackages) {
+                mSettings.writeLPr();
+            }
+        }
+    };
+
     private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
             boolean killApp, boolean virtualPreload, String[] grantedPermissions,
             boolean launchedForRestore, String installerPackage,
@@ -1926,7 +1966,10 @@
             // review flag which is used to emulate runtime permissions for
             // legacy apps.
             if (grantPermissions) {
-                grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions);
+                final int callingUid = Binder.getCallingUid();
+                mPermissionManager.grantRequestedRuntimePermissions(
+                        res.pkg, res.newUsers, grantedPermissions, callingUid,
+                        mPermissionCallback);
             }
 
             final boolean update = res.removedInfo != null
@@ -1942,9 +1985,9 @@
             // app that had no children, we grant requested runtime permissions to the new
             // children if the parent on the system image had them already granted.
             if (res.pkg.parentPackage != null) {
-                synchronized (mPackages) {
-                    grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(res.pkg);
-                }
+                final int callingUid = Binder.getCallingUid();
+                mPermissionManager.grantRuntimePermissionsGrantedToDisabledPackage(
+                        res.pkg, callingUid, mPermissionCallback);
             }
 
             synchronized (mPackages) {
@@ -2109,39 +2152,6 @@
         }
     }
 
-    private void grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(
-            PackageParser.Package pkg) {
-        if (pkg.parentPackage == null) {
-            return;
-        }
-        if (pkg.requestedPermissions == null) {
-            return;
-        }
-        final PackageSetting disabledSysParentPs = mSettings
-                .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
-        if (disabledSysParentPs == null || disabledSysParentPs.pkg == null
-                || !disabledSysParentPs.isPrivileged()
-                || (disabledSysParentPs.childPackageNames != null
-                        && !disabledSysParentPs.childPackageNames.isEmpty())) {
-            return;
-        }
-        final int[] allUserIds = sUserManager.getUserIds();
-        final int permCount = pkg.requestedPermissions.size();
-        for (int i = 0; i < permCount; i++) {
-            String permission = pkg.requestedPermissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(permission);
-            if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
-                continue;
-            }
-            for (int userId : allUserIds) {
-                if (disabledSysParentPs.getPermissionsState().hasRuntimePermission(
-                        permission, userId)) {
-                    grantRuntimePermission(pkg.packageName, permission, userId);
-                }
-            }
-        }
-    }
-
     private StorageEventListener mStorageListener = new StorageEventListener() {
         @Override
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -2164,14 +2174,6 @@
                     unloadPrivatePackages(vol);
                 }
             }
-
-            if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isPrimary()) {
-                if (vol.state == VolumeInfo.STATE_MOUNTED) {
-                    updateExternalMediaStatus(true, false);
-                } else if (vol.state == VolumeInfo.STATE_EJECTING) {
-                    updateExternalMediaStatus(false, false);
-                }
-            }
         }
 
         @Override
@@ -2202,58 +2204,6 @@
         }
     };
 
-    private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
-            String[] grantedPermissions) {
-        for (int userId : userIds) {
-            grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions);
-        }
-    }
-
-    private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
-            String[] grantedPermissions) {
-        PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-
-        PermissionsState permissionsState = ps.getPermissionsState();
-
-        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-
-        final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
-                >= Build.VERSION_CODES.M;
-
-        final boolean instantApp = isInstantApp(pkg.packageName, userId);
-
-        for (String permission : pkg.requestedPermissions) {
-            final BasePermission bp;
-            synchronized (mPackages) {
-                bp = mSettings.mPermissions.get(permission);
-            }
-            if (bp != null && (bp.isRuntime() || bp.isDevelopment())
-                    && (!instantApp || bp.isInstant())
-                    && (supportsRuntimePermissions || !bp.isRuntimeOnly())
-                    && (grantedPermissions == null
-                           || ArrayUtils.contains(grantedPermissions, permission))) {
-                final int flags = permissionsState.getPermissionFlags(permission, userId);
-                if (supportsRuntimePermissions) {
-                    // Installer cannot change immutable permissions.
-                    if ((flags & immutableFlags) == 0) {
-                        grantRuntimePermission(pkg.packageName, permission, userId);
-                    }
-                } else if (mPermissionReviewRequired) {
-                    // In permission review mode we clear the review flag when we
-                    // are asked to install the app with all permissions granted.
-                    if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                        updatePermissionFlags(permission, pkg.packageName,
-                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
-                    }
-                }
-            }
-        }
-    }
-
     Bundle extrasForInstallResult(PackageInstalledInfo res) {
         Bundle extras = null;
         switch (res.returnCode) {
@@ -2422,7 +2372,29 @@
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
         mMetrics = new DisplayMetrics();
-        mSettings = new Settings(mPackages);
+        mInstaller = installer;
+
+        // Create sub-components that provide services / data. Order here is important.
+        synchronized (mInstallLock) {
+        synchronized (mPackages) {
+            // Expose private service for system components to use.
+            LocalServices.addService(
+                    PackageManagerInternal.class, new PackageManagerInternalImpl());
+            sUserManager = new UserManagerService(context, this,
+                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
+            mPermissionManager = PermissionManagerService.create(context,
+                    new DefaultPermissionGrantedCallback() {
+                        @Override
+                        public void onDefaultRuntimePermissionsGranted(int userId) {
+                            synchronized(mPackages) {
+                                mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
+                            }
+                        }
+                    }, mPackages /*externalLock*/);
+            mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
+            mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages);
+        }
+        }
         mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
         mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
@@ -2453,7 +2425,6 @@
             mSeparateProcesses = null;
         }
 
-        mInstaller = installer;
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
         mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
@@ -2482,32 +2453,12 @@
             mHandler = new PackageHandler(mHandlerThread.getLooper());
             mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
-
-            mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);
             mInstantAppRegistry = new InstantAppRegistry(this);
 
             File dataDir = Environment.getDataDirectory();
             mAppInstallDir = new File(dataDir, "app");
             mAppLib32InstallDir = new File(dataDir, "app-lib");
-            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
-            sUserManager = new UserManagerService(context, this,
-                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
-
-            // Propagate permission configuration in to package manager.
-            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
-                    = systemConfig.getPermissions();
-            for (int i=0; i<permConfig.size(); i++) {
-                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
-                BasePermission bp = mSettings.mPermissions.get(perm.name);
-                if (bp == null) {
-                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
-                    mSettings.mPermissions.put(perm.name, bp);
-                }
-                if (perm.gids != null) {
-                    bp.setGids(perm.gids, perm.perUser);
-                }
-            }
 
             ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
             final int builtInLibCount = libConfig.size();
@@ -3110,8 +3061,6 @@
         // once we have a booted system.
         mInstaller.setWarnIfHeld(mPackages);
 
-        // Expose private service for system components to use.
-        LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
@@ -3314,6 +3263,24 @@
             removeCodePathLI(dstCodePath);
             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;
     }
 
@@ -3909,7 +3876,7 @@
     public boolean isPackageAvailable(String packageName, int userId) {
         if (!sUserManager.exists(userId)) return false;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "is package available");
         synchronized (mPackages) {
             PackageParser.Package p = mPackages.get(packageName);
@@ -3952,7 +3919,7 @@
             int flags, int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get package info");
 
         // reader
@@ -4214,7 +4181,7 @@
         if (!sUserManager.exists(userId)) return -1;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getPackageUid");
 
         // reader
@@ -4244,7 +4211,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getPackageGids");
 
         // reader
@@ -4271,116 +4238,23 @@
         return null;
     }
 
-    static PermissionInfo generatePermissionInfo(BasePermission bp, int flags) {
-        if (bp.perm != null) {
-            return PackageParser.generatePermissionInfo(bp.perm, flags);
-        }
-        PermissionInfo pi = new PermissionInfo();
-        pi.name = bp.name;
-        pi.packageName = bp.sourcePackage;
-        pi.nonLocalizedLabel = bp.name;
-        pi.protectionLevel = bp.protectionLevel;
-        return pi;
-    }
-
     @Override
     public PermissionInfo getPermissionInfo(String name, String packageName, int flags) {
-        final int callingUid = Binder.getCallingUid();
-        if (getInstantAppPackageName(callingUid) != null) {
-            return null;
-        }
-        // reader
-        synchronized (mPackages) {
-            final BasePermission p = mSettings.mPermissions.get(name);
-            if (p == null) {
-                return null;
-            }
-            // If the caller is an app that targets pre 26 SDK drop protection flags.
-            PermissionInfo permissionInfo = generatePermissionInfo(p, flags);
-            if (permissionInfo != null) {
-                final int protectionLevel = adjustPermissionProtectionFlagsLPr(
-                        permissionInfo.protectionLevel, packageName, callingUid);
-                if (permissionInfo.protectionLevel != protectionLevel) {
-                    // If we return different protection level, don't use the cached info
-                    if (p.perm != null && p.perm.info == permissionInfo) {
-                        permissionInfo = new PermissionInfo(permissionInfo);
-                    }
-                    permissionInfo.protectionLevel = protectionLevel;
-                }
-            }
-            return permissionInfo;
-        }
-    }
-
-    private int adjustPermissionProtectionFlagsLPr(int protectionLevel,
-            String packageName, int uid) {
-        // Signature permission flags area always reported
-        final int protectionLevelMasked = protectionLevel
-                & (PermissionInfo.PROTECTION_NORMAL
-                | PermissionInfo.PROTECTION_DANGEROUS
-                | PermissionInfo.PROTECTION_SIGNATURE);
-        if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
-            return protectionLevel;
-        }
-
-        // System sees all flags.
-        final int appId = UserHandle.getAppId(uid);
-        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
-                || appId == Process.SHELL_UID) {
-            return protectionLevel;
-        }
-
-        // Normalize package name to handle renamed packages and static libs
-        packageName = resolveInternalPackageNameLPr(packageName,
-                PackageManager.VERSION_CODE_HIGHEST);
-
-        // Apps that target O see flags for all protection levels.
-        final PackageSetting ps = mSettings.mPackages.get(packageName);
-        if (ps == null) {
-            return protectionLevel;
-        }
-        if (ps.appId != appId) {
-            return protectionLevel;
-        }
-
-        final PackageParser.Package pkg = mPackages.get(packageName);
-        if (pkg == null) {
-            return protectionLevel;
-        }
-        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
-            return protectionLevelMasked;
-        }
-
-        return protectionLevel;
+        return mPermissionManager.getPermissionInfo(name, packageName, flags, getCallingUid());
     }
 
     @Override
-    public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String group,
+    public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
             int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        // reader
+        // TODO Move this to PermissionManager when mPermissionGroups is moved there
         synchronized (mPackages) {
-            if (group != null && !mPermissionGroups.containsKey(group)) {
+            if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
                 // This is thrown as NameNotFoundException
                 return null;
             }
-
-            ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
-            for (BasePermission p : mSettings.mPermissions.values()) {
-                if (group == null) {
-                    if (p.perm == null || p.perm.info.group == null) {
-                        out.add(generatePermissionInfo(p, flags));
-                    }
-                } else {
-                    if (p.perm != null && group.equals(p.perm.info.group)) {
-                        out.add(PackageParser.generatePermissionInfo(p.perm, flags));
-                    }
-                }
-            }
-            return new ParceledListSlice<>(out);
         }
+        return new ParceledListSlice<>(
+                mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()));
     }
 
     @Override
@@ -4455,7 +4329,7 @@
             int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForApplication(flags, userId, packageName);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get application info");
 
         // writer
@@ -4764,7 +4638,8 @@
             triaged = false;
         }
         if ((flags & PackageManager.MATCH_ANY_USER) != 0) {
-            enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
+            mPermissionManager.enforceCrossUserPermission(
+                    Binder.getCallingUid(), userId, false, false,
                     "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission at "
                     + Debug.getCallers(5));
         } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0 && isCallerSystemUser
@@ -4893,7 +4768,7 @@
             int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get activity info");
         synchronized (mPackages) {
             PackageParser.Activity a = mActivities.mActivities.get(component);
@@ -4952,7 +4827,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get receiver info");
         synchronized (mPackages) {
             PackageParser.Activity a = mReceivers.mActivities.get(component);
@@ -5089,7 +4964,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get service info");
         synchronized (mPackages) {
             PackageParser.Service s = mServices.mServices.get(component);
@@ -5113,7 +4988,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get provider info");
         synchronized (mPackages) {
             PackageParser.Provider p = mProviders.mProviders.get(component);
@@ -5276,39 +5151,7 @@
 
     @Override
     public int checkPermission(String permName, String pkgName, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return PackageManager.PERMISSION_DENIED;
-        }
-        final int callingUid = Binder.getCallingUid();
-
-        synchronized (mPackages) {
-            final PackageParser.Package p = mPackages.get(pkgName);
-            if (p != null && p.mExtras != null) {
-                final PackageSetting ps = (PackageSetting) p.mExtras;
-                if (filterAppAccessLPr(ps, callingUid, userId)) {
-                    return PackageManager.PERMISSION_DENIED;
-                }
-                final boolean instantApp = ps.getInstantApp(userId);
-                final PermissionsState permissionsState = ps.getPermissionsState();
-                if (permissionsState.hasPermission(permName, userId)) {
-                    if (instantApp) {
-                        BasePermission bp = mSettings.mPermissions.get(permName);
-                        if (bp != null && bp.isInstant()) {
-                            return PackageManager.PERMISSION_GRANTED;
-                        }
-                    } else {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                }
-                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
-                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
-                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
-                    return PackageManager.PERMISSION_GRANTED;
-                }
-            }
-        }
-
-        return PackageManager.PERMISSION_DENIED;
+        return mPermissionManager.checkPermission(permName, pkgName, getCallingUid(), userId);
     }
 
     @Override
@@ -5339,8 +5182,7 @@
                 final PermissionsState permissionsState = settingBase.getPermissionsState();
                 if (permissionsState.hasPermission(permName, userId)) {
                     if (isUidInstantApp) {
-                        BasePermission bp = mSettings.mPermissions.get(permName);
-                        if (bp != null && bp.isInstant()) {
+                        if (mPermissionManager.isPermissionInstant(permName)) {
                             return PackageManager.PERMISSION_GRANTED;
                         }
                     } else {
@@ -5409,448 +5251,49 @@
         }
     }
 
-    /**
-     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
-     * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
-     * @param checkShell whether to prevent shell from access if there's a debugging restriction
-     * @param message the message to log on security exception
-     */
-    void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
-            boolean checkShell, String message) {
-        if (userId < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userId);
-        }
-        if (checkShell) {
-            enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
-        }
-        if (userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
-            if (requireFullPermission) {
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            } else {
-                try {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-                } catch (SecurityException se) {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
-                }
-            }
-        }
-    }
-
-    void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
-        if (callingUid == Process.SHELL_UID) {
-            if (userHandle >= 0
-                    && sUserManager.hasUserRestriction(restriction, userHandle)) {
-                throw new SecurityException("Shell does not have permission to access user "
-                        + userHandle);
-            } else if (userHandle < 0) {
-                Slog.e(TAG, "Unable to check shell permission for user " + userHandle + "\n\t"
-                        + Debug.getCallers(3));
-            }
-        }
-    }
-
-    private BasePermission findPermissionTreeLP(String permName) {
-        for(BasePermission bp : mSettings.mPermissionTrees.values()) {
-            if (permName.startsWith(bp.name) &&
-                    permName.length() > bp.name.length() &&
-                    permName.charAt(bp.name.length()) == '.') {
-                return bp;
-            }
-        }
-        return null;
-    }
-
-    private BasePermission checkPermissionTreeLP(String permName) {
-        if (permName != null) {
-            BasePermission bp = findPermissionTreeLP(permName);
-            if (bp != null) {
-                if (bp.uid == UserHandle.getAppId(Binder.getCallingUid())) {
-                    return bp;
-                }
-                throw new SecurityException("Calling uid "
-                        + Binder.getCallingUid()
-                        + " is not allowed to add to permission tree "
-                        + bp.name + " owned by uid " + bp.uid);
-            }
-        }
-        throw new SecurityException("No permission tree found for " + permName);
-    }
-
-    static boolean compareStrings(CharSequence s1, CharSequence s2) {
-        if (s1 == null) {
-            return s2 == null;
-        }
-        if (s2 == null) {
-            return false;
-        }
-        if (s1.getClass() != s2.getClass()) {
-            return false;
-        }
-        return s1.equals(s2);
-    }
-
-    static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
-        if (pi1.icon != pi2.icon) return false;
-        if (pi1.logo != pi2.logo) return false;
-        if (pi1.protectionLevel != pi2.protectionLevel) return false;
-        if (!compareStrings(pi1.name, pi2.name)) return false;
-        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
-        // We'll take care of setting this one.
-        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
-        // These are not currently stored in settings.
-        //if (!compareStrings(pi1.group, pi2.group)) return false;
-        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
-        //if (pi1.labelRes != pi2.labelRes) return false;
-        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
-        return true;
-    }
-
-    int permissionInfoFootprint(PermissionInfo info) {
-        int size = info.name.length();
-        if (info.nonLocalizedLabel != null) size += info.nonLocalizedLabel.length();
-        if (info.nonLocalizedDescription != null) size += info.nonLocalizedDescription.length();
-        return size;
-    }
-
-    int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
-        int size = 0;
-        for (BasePermission perm : mSettings.mPermissions.values()) {
-            if (perm.uid == tree.uid) {
-                size += perm.name.length() + permissionInfoFootprint(perm.perm.info);
-            }
-        }
-        return size;
-    }
-
-    void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
-        // We calculate the max size of permissions defined by this uid and throw
-        // if that plus the size of 'info' would exceed our stated maximum.
-        if (tree.uid != Process.SYSTEM_UID) {
-            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
-            if (curTreeSize + permissionInfoFootprint(info) > MAX_PERMISSION_TREE_FOOTPRINT) {
-                throw new SecurityException("Permission tree size cap exceeded");
-            }
-        }
-    }
-
-    boolean addPermissionLocked(PermissionInfo info, boolean async) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant apps can't add permissions");
-        }
-        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
-            throw new SecurityException("Label must be specified in permission");
-        }
-        BasePermission tree = checkPermissionTreeLP(info.name);
-        BasePermission bp = mSettings.mPermissions.get(info.name);
-        boolean added = bp == null;
-        boolean changed = true;
-        int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
-        if (added) {
-            enforcePermissionCapLocked(info, tree);
-            bp = new BasePermission(info.name, tree.sourcePackage,
-                    BasePermission.TYPE_DYNAMIC);
-        } else if (bp.type != BasePermission.TYPE_DYNAMIC) {
-            throw new SecurityException(
-                    "Not allowed to modify non-dynamic permission "
-                    + info.name);
-        } else {
-            if (bp.protectionLevel == fixedLevel
-                    && bp.perm.owner.equals(tree.perm.owner)
-                    && bp.uid == tree.uid
-                    && comparePermissionInfos(bp.perm.info, info)) {
-                changed = false;
-            }
-        }
-        bp.protectionLevel = fixedLevel;
-        info = new PermissionInfo(info);
-        info.protectionLevel = fixedLevel;
-        bp.perm = new PackageParser.Permission(tree.perm.owner, info);
-        bp.perm.info.packageName = tree.perm.info.packageName;
-        bp.uid = tree.uid;
-        if (added) {
-            mSettings.mPermissions.put(info.name, bp);
-        }
-        if (changed) {
-            if (!async) {
-                mSettings.writeLPr();
-            } else {
-                scheduleWriteSettingsLocked();
-            }
-        }
-        return added;
+    boolean addPermission(PermissionInfo info, final boolean async) {
+        return mPermissionManager.addPermission(
+                info, async, getCallingUid(), new PermissionCallback() {
+                    @Override
+                    public void onPermissionChanged() {
+                        if (!async) {
+                            mSettings.writeLPr();
+                        } else {
+                            scheduleWriteSettingsLocked();
+                        }
+                    }
+                });
     }
 
     @Override
     public boolean addPermission(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermissionLocked(info, false);
+            return addPermission(info, false);
         }
     }
 
     @Override
     public boolean addPermissionAsync(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermissionLocked(info, true);
+            return addPermission(info, true);
         }
     }
 
     @Override
-    public void removePermission(String name) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant applications don't have access to this method");
-        }
-        synchronized (mPackages) {
-            checkPermissionTreeLP(name);
-            BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp != null) {
-                if (bp.type != BasePermission.TYPE_DYNAMIC) {
-                    throw new SecurityException(
-                            "Not allowed to modify non-dynamic permission "
-                            + name);
-                }
-                mSettings.mPermissions.remove(name);
-                mSettings.writeLPr();
-            }
-        }
-    }
-
-    private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(
-            PackageParser.Package pkg, BasePermission bp) {
-        int index = pkg.requestedPermissions.indexOf(bp.name);
-        if (index == -1) {
-            throw new SecurityException("Package " + pkg.packageName
-                    + " has not requested permission " + bp.name);
-        }
-        if (!bp.isRuntime() && !bp.isDevelopment()) {
-            throw new SecurityException("Permission " + bp.name
-                    + " is not a changeable permission type");
-        }
+    public void removePermission(String permName) {
+        mPermissionManager.removePermission(permName, getCallingUid(), mPermissionCallback);
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String name, final int userId) {
-        grantRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
-    }
-
-    private void grantRuntimePermission(String packageName, String name, final int userId,
-            boolean overridePolicy) {
-        if (!sUserManager.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-        final int callingUid = Binder.getCallingUid();
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
-                "grantRuntimePermission");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "grantRuntimePermission");
-
-        final int uid;
-        final PackageSetting ps;
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-            ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-
-            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (mPermissionReviewRequired
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                    && bp.isRuntime()) {
-                return;
-            }
-
-            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
-
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
-            final int flags = permissionsState.getPermissionFlags(name, userId);
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-                throw new SecurityException("Cannot grant system fixed permission "
-                        + name + " for package " + packageName);
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                throw new SecurityException("Cannot grant policy fixed permission "
-                        + name + " for package " + packageName);
-            }
-
-            if (bp.isDevelopment()) {
-                // Development permissions must be handled specially, since they are not
-                // normal runtime permissions.  For now they apply to all users.
-                if (permissionsState.grantInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    scheduleWriteSettingsLocked();
-                }
-                return;
-            }
-
-            if (ps.getInstantApp(userId) && !bp.isInstant()) {
-                throw new SecurityException("Cannot grant non-ephemeral permission"
-                        + name + " for package " + packageName);
-            }
-
-            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-                Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
-                return;
-            }
-
-            final int result = permissionsState.grantRuntimePermission(bp, userId);
-            switch (result) {
-                case PermissionsState.PERMISSION_OPERATION_FAILURE: {
-                    return;
-                }
-
-                case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
-                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
-                        }
-                    });
-                }
-                break;
-            }
-
-            if (bp.isRuntime()) {
-                logPermissionGranted(mContext, name, packageName);
-            }
-
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
-            // Not critical if that is lost - app has to request again.
-            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-        }
-
-        // Only need to do this if user is initialized. Otherwise it's a new user
-        // and there are no processes running as the user yet and there's no need
-        // to make an expensive call to remount processes for the changed permissions.
-        if (READ_EXTERNAL_STORAGE.equals(name)
-                || WRITE_EXTERNAL_STORAGE.equals(name)) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                if (sUserManager.isInitialized(userId)) {
-                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
-                            StorageManagerInternal.class);
-                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
+    public void grantRuntimePermission(String packageName, String permName, final int userId) {
+        mPermissionManager.grantRuntimePermission(permName, packageName, false /*overridePolicy*/,
+                getCallingUid(), userId, mPermissionCallback);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String name, int userId) {
-        revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
-    }
-
-    private void revokeRuntimePermission(String packageName, String name, int userId,
-            boolean overridePolicy) {
-        if (!sUserManager.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
-                "revokeRuntimePermission");
-
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "revokeRuntimePermission");
-
-        final int appId;
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-
-            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (mPermissionReviewRequired
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                    && bp.isRuntime()) {
-                return;
-            }
-
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
-            final int flags = permissionsState.getPermissionFlags(name, userId);
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-                throw new SecurityException("Cannot revoke system fixed permission "
-                        + name + " for package " + packageName);
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                throw new SecurityException("Cannot revoke policy fixed permission "
-                        + name + " for package " + packageName);
-            }
-
-            if (bp.isDevelopment()) {
-                // Development permissions must be handled specially, since they are not
-                // normal runtime permissions.  For now they apply to all users.
-                if (permissionsState.revokeInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    scheduleWriteSettingsLocked();
-                }
-                return;
-            }
-
-            if (permissionsState.revokeRuntimePermission(bp, userId) ==
-                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                return;
-            }
-
-            if (bp.isRuntime()) {
-                logPermissionRevoked(mContext, name, packageName);
-            }
-
-            mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
-
-            // Critical, after this call app should never have the permission.
-            mSettings.writeRuntimePermissionsForUserLPr(userId, true);
-
-            appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-        }
-
-        killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+    public void revokeRuntimePermission(String packageName, String permName, int userId) {
+        mPermissionManager.revokeRuntimePermission(permName, packageName, false /*overridePolicy*/,
+                getCallingUid(), userId, mPermissionCallback);
     }
 
     /**
@@ -5943,91 +5386,16 @@
     }
 
     @Override
-    public int getPermissionFlags(String name, String packageName, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return 0;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
-
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, false /* checkShell */,
-                "getPermissionFlags");
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                return 0;
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                return 0;
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                return 0;
-            }
-            PermissionsState permissionsState = ps.getPermissionsState();
-            return permissionsState.getPermissionFlags(name, userId);
-        }
+    public int getPermissionFlags(String permName, String packageName, int userId) {
+        return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId);
     }
 
     @Override
-    public void updatePermissionFlags(String name, String packageName, int flagMask,
+    public void updatePermissionFlags(String permName, String packageName, int flagMask,
             int flagValues, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
-
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "updatePermissionFlags");
-
-        // Only the system can change these flags and nothing else.
-        if (getCallingUid() != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-        }
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-
-            PermissionsState permissionsState = ps.getPermissionsState();
-
-            boolean hadState = permissionsState.getRuntimePermissionState(name, userId) != null;
-
-            if (permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues)) {
-                // Install and runtime permissions are stored in different places,
-                // so figure out what permission changed and persist the change.
-                if (permissionsState.getInstallPermissionState(name) != null) {
-                    scheduleWriteSettingsLocked();
-                } else if (permissionsState.getRuntimePermissionState(name, userId) != null
-                        || hadState) {
-                    mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-                }
-            }
-        }
+        mPermissionManager.updatePermissionFlags(
+                permName, packageName, flagMask, flagValues, getCallingUid(), userId,
+                mPermissionCallback);
     }
 
     /**
@@ -6036,52 +5404,16 @@
      */
     @Override
     public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlagsForAllApps");
-
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "updatePermissionFlagsForAllApps");
-
-        // Only the system can change system fixed flags.
-        if (getCallingUid() != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-        }
-
         synchronized (mPackages) {
-            boolean changed = false;
-            final int packageCount = mPackages.size();
-            for (int pkgIndex = 0; pkgIndex < packageCount; pkgIndex++) {
-                final PackageParser.Package pkg = mPackages.valueAt(pkgIndex);
-                final PackageSetting ps = (PackageSetting) pkg.mExtras;
-                if (ps == null) {
-                    continue;
-                }
-                PermissionsState permissionsState = ps.getPermissionsState();
-                changed |= permissionsState.updatePermissionFlagsForAllPermissions(
-                        userId, flagMask, flagValues);
-            }
+            final boolean changed = mPermissionManager.updatePermissionFlagsForAllApps(
+                    flagMask, flagValues, getCallingUid(), userId, mPackages.values(),
+                    mPermissionCallback);
             if (changed) {
                 mSettings.writeRuntimePermissionsForUserLPr(userId, false);
             }
         }
     }
 
-    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED
-            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(message + " requires "
-                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
-                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
-        }
-    }
-
     @Override
     public boolean shouldShowRequestPermissionRationale(String permissionName,
             String packageName, int userId) {
@@ -6273,7 +5605,7 @@
      * <br />
      * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
      */
-    static int compareSignatures(Signature[] s1, Signature[] s2) {
+    public static int compareSignatures(Signature[] s1, Signature[] s2) {
         if (s1 == null) {
             return s2 == null
                     ? PackageManager.SIGNATURE_NEITHER_SIGNED
@@ -6627,9 +5959,14 @@
     public ResolveInfo resolveIntent(Intent intent, String resolvedType,
             int flags, int userId) {
         return resolveIntentInternal(
-                intent, resolvedType, flags, userId, false /*includeInstantApps*/);
+                intent, resolvedType, flags, userId, false /*resolveForStart*/);
     }
 
+    /**
+     * Normally instant apps can only be resolved when they're visible to the caller.
+     * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
+     * since we need to allow the system to start any installed application.
+     */
     private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
             int flags, int userId, boolean resolveForStart) {
         try {
@@ -6638,7 +5975,7 @@
             if (!sUserManager.exists(userId)) return null;
             final int callingUid = Binder.getCallingUid();
             flags = updateFlagsForResolve(flags, userId, intent, callingUid, resolveForStart);
-            enforceCrossUserPermission(callingUid, userId,
+            mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                     false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");
 
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
@@ -7219,7 +6556,7 @@
             boolean resolveForStart, boolean allowDynamicSplits) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
         final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */,
                 "query intent activities");
         final String pkgName = intent.getPackage();
@@ -7974,7 +7311,7 @@
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForResolve(flags, userId, intent, callingUid,
                 false /*includeInstantApps*/);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent activity options");
         final String resultsAction = intent.getAction();
@@ -8155,7 +7492,7 @@
             String resolvedType, int flags, int userId, boolean allowDynamicSplits) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8267,7 +7604,7 @@
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8507,7 +7844,7 @@
         if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForPackage(flags, userId, null);
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get installed packages");
 
@@ -8594,7 +7931,7 @@
             String[] permissions, int flags, int userId) {
         if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForPackage(flags, userId, permissions);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get packages holding permissions");
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
@@ -8699,7 +8036,7 @@
             mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
                     "getEphemeralApplications");
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getEphemeralApplications");
         synchronized (mPackages) {
@@ -8714,7 +8051,7 @@
 
     @Override
     public boolean isInstantApp(String packageName, int userId) {
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "isInstantApp");
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
@@ -8747,7 +8084,7 @@
             return null;
         }
 
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppCookie");
         if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8765,7 +8102,7 @@
             return true;
         }
 
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setInstantAppCookie");
         if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8787,7 +8124,7 @@
             mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
                     "getInstantAppIcon");
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppIcon");
 
@@ -8847,6 +8184,10 @@
 
     @Override
     public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+        return resolveContentProviderInternal(name, flags, userId);
+    }
+
+    private ProviderInfo resolveContentProviderInternal(String name, int flags, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId, name);
         final String instantAppPkgName = getInstantAppPackageName(Binder.getCallingUid());
@@ -9105,7 +8446,7 @@
         return fname;
     }
 
-    static void reportSettingsProblem(int priority, String msg) {
+    public static void reportSettingsProblem(int priority, String msg) {
         logCriticalInfo(priority, msg);
     }
 
@@ -9738,7 +9079,7 @@
      * and {@code numberOfPackagesFailed}.
      */
     private int[] performDexOptUpgrade(List<PackageParser.Package> pkgs, boolean showDialog,
-            String compilerFilter, boolean bootComplete) {
+            final String compilerFilter, boolean bootComplete) {
 
         int numberOfPackagesVisited = 0;
         int numberOfPackagesOptimized = 0;
@@ -9749,6 +9090,8 @@
         for (PackageParser.Package pkg : pkgs) {
             numberOfPackagesVisited++;
 
+            boolean useProfileForDexopt = false;
+
             if ((isFirstBoot() || isUpgrade()) && isSystemApp(pkg)) {
                 // Copy over initial preopt profiles since we won't get any JIT samples for methods
                 // that are already compiled.
@@ -9762,11 +9105,30 @@
                         if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
                                 pkg.applicationInfo.uid, pkg.packageName)) {
                             Log.e(TAG, "Installer failed to copy system profile!");
+                        } else {
+                            // Disabled as this causes speed-profile compilation during first boot
+                            // even if things are already compiled.
+                            // useProfileForDexopt = true;
                         }
                     } catch (Exception e) {
                         Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
                                 e);
                     }
+                } else {
+                    PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);
+                    // Handle compressed APKs in this path. Only do this for stubs with profiles to
+                    // minimize the number off apps being speed-profile compiled during first boot.
+                    // The other paths will not change the filter.
+                    if (disabledPs != null && disabledPs.pkg.isStub) {
+                        // The package is the stub one, remove the stub suffix to get the normal
+                        // 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();
+                    }
                 }
             }
 
@@ -9795,17 +9157,13 @@
                 }
             }
 
-            // If the OTA updates a system app which was previously preopted to a non-preopted state
-            // the app might end up being verified at runtime. That's because by default the apps
-            // are verify-profile but for preopted apps there's no profile.
-            // Do a hacky check to ensure that if we have no profiles (a reasonable indication
-            // that before the OTA the app was preopted) the app gets compiled with a non-profile
-            // filter (by default 'quicken').
-            // Note that at this stage unused apps are already filtered.
-            if (isSystemApp(pkg) &&
-                    DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
-                    !Environment.getReferenceProfile(pkg.packageName).exists()) {
-                compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter);
+            String pkgCompilerFilter = compilerFilter;
+            if (useProfileForDexopt) {
+                // Use background dexopt mode to try and use the profile. Note that this does not
+                // guarantee usage of the profile.
+                pkgCompilerFilter =
+                        PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+                                PackageManagerService.REASON_BACKGROUND_DEXOPT);
             }
 
             // checkProfiles is false to avoid merging profiles during boot which
@@ -9816,22 +9174,9 @@
             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
             int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
                     pkg.packageName,
-                    compilerFilter,
+                    pkgCompilerFilter,
                     dexoptFlags));
 
-            if (pkg.isSystemApp()) {
-                // Only dexopt shared secondary dex files belonging to system apps to not slow down
-                // too much boot after an OTA.
-                int secondaryDexoptFlags = dexoptFlags |
-                        DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
-                        DexoptOptions.DEXOPT_ONLY_SHARED_DEX;
-                mDexManager.dexoptSecondaryDex(new DexoptOptions(
-                        pkg.packageName,
-                        compilerFilter,
-                        secondaryDexoptFlags));
-            }
-
-            // TODO(shubhamajmera): Record secondary dexopt stats.
             switch (primaryDexOptStaus) {
                 case PackageDexOptimizer.DEX_OPT_PERFORMED:
                     numberOfPackagesOptimized++;
@@ -10388,7 +9733,8 @@
         }
     }
 
-    private void addSharedLibraryLPr(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
+    private void addSharedLibraryLPr(Set<String> usesLibraryFiles,
+            SharedLibraryEntry file,
             PackageParser.Package changingLib) {
         if (file.path != null) {
             usesLibraryFiles.add(file.path);
@@ -10417,7 +9763,10 @@
         if (pkg == null) {
             return;
         }
-        ArraySet<String> usesLibraryFiles = null;
+        // The collection used here must maintain the order of addition (so
+        // that libraries are searched in the correct order) and must have no
+        // duplicates.
+        Set<String> usesLibraryFiles = null;
         if (pkg.usesLibraries != null) {
             usesLibraryFiles = addSharedLibrariesLPw(pkg.usesLibraries,
                     null, null, pkg.packageName, changingLib, true,
@@ -10441,10 +9790,10 @@
         }
     }
 
-    private ArraySet<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
+    private Set<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
             @Nullable int[] requiredVersions, @Nullable String[][] requiredCertDigests,
             @NonNull String packageName, @Nullable PackageParser.Package changingLib,
-            boolean required, int targetSdk, @Nullable ArraySet<String> outUsedLibraries)
+            boolean required, int targetSdk, @Nullable Set<String> outUsedLibraries)
             throws PackageManagerException {
         final int libCount = requestedLibraries.size();
         for (int i = 0; i < libCount; i++) {
@@ -10510,7 +9859,9 @@
                 }
 
                 if (outUsedLibraries == null) {
-                    outUsedLibraries = new ArraySet<>();
+                    // Use LinkedHashSet to preserve the order of files added to
+                    // usesLibraryFiles while eliminating duplicates.
+                    outUsedLibraries = new LinkedHashSet<>();
                 }
                 addSharedLibraryLPr(outUsedLibraries, libEntry, changingLib);
             }
@@ -10703,6 +10054,12 @@
 
         assertPackageIsValid(pkg, policyFlags, scanFlags);
 
+        if (Build.IS_DEBUGGABLE &&
+                pkg.isPrivilegedApp() &&
+                !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+            PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
+        }
+
         // Initialize package source and resource directories
         final File scanFile = new File(pkg.codePath);
         final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -11866,84 +11223,25 @@
                     }
                 }
 
-                ArrayMap<String, BasePermission> permissionMap =
-                        p.tree ? mSettings.mPermissionTrees
-                                : mSettings.mPermissions;
-                BasePermission bp = permissionMap.get(p.info.name);
-
-                // Allow system apps to redefine non-system permissions
-                if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) {
-                    final boolean currentOwnerIsSystem = (bp.perm != null
-                            && isSystemApp(bp.perm.owner));
-                    if (isSystemApp(p.owner)) {
-                        if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
-                            // It's a built-in permission and no owner, take ownership now
-                            bp.packageSetting = pkgSetting;
-                            bp.perm = p;
-                            bp.uid = pkg.applicationInfo.uid;
-                            bp.sourcePackage = p.info.packageName;
-                            p.info.flags |= PermissionInfo.FLAG_INSTALLED;
-                        } else if (!currentOwnerIsSystem) {
-                            String msg = "New decl " + p.owner + " of permission  "
-                                    + p.info.name + " is system; overriding " + bp.sourcePackage;
-                            reportSettingsProblem(Log.WARN, msg);
-                            bp = null;
-                        }
-                    }
-                }
-
-                if (bp == null) {
-                    bp = new BasePermission(p.info.name, p.info.packageName,
-                            BasePermission.TYPE_NORMAL);
+                // 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);
                 }
-
-                if (bp.perm == null) {
-                    if (bp.sourcePackage == null
-                            || bp.sourcePackage.equals(p.info.packageName)) {
-                        BasePermission tree = findPermissionTreeLP(p.info.name);
-                        if (tree == null
-                                || tree.sourcePackage.equals(p.info.packageName)) {
-                            bp.packageSetting = pkgSetting;
-                            bp.perm = p;
-                            bp.uid = pkg.applicationInfo.uid;
-                            bp.sourcePackage = p.info.packageName;
-                            p.info.flags |= PermissionInfo.FLAG_INSTALLED;
-                            if (chatty) {
-                                if (r == null) {
-                                    r = new StringBuilder(256);
-                                } else {
-                                    r.append(' ');
-                                }
-                                r.append(p.info.name);
-                            }
-                        } else {
-                            Slog.w(TAG, "Permission " + p.info.name + " from package "
-                                    + p.info.packageName + " ignored: base tree "
-                                    + tree.name + " is from package "
-                                    + tree.sourcePackage);
-                        }
-                    } else {
-                        Slog.w(TAG, "Permission " + p.info.name + " from package "
-                                + p.info.packageName + " ignored: original from "
-                                + bp.sourcePackage);
-                    }
-                } else if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append("DUP:");
-                    r.append(p.info.name);
-                }
-                if (bp.perm == p) {
-                    bp.protectionLevel = p.info.protectionLevel;
-                }
-            }
-
-            if (r != null) {
-                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permissions: " + r);
             }
 
             N = pkg.instrumentation.size();
@@ -12673,12 +11971,12 @@
         r = null;
         for (i=0; i<N; i++) {
             PackageParser.Permission p = pkg.permissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(p.info.name);
+            BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name);
             if (bp == null) {
                 bp = mSettings.mPermissionTrees.get(p.info.name);
             }
-            if (bp != null && bp.perm == p) {
-                bp.perm = null;
+            if (bp != null && bp.isPermission(p)) {
+                bp.setPermission(null);
                 if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
@@ -12703,8 +12001,7 @@
         r = null;
         for (i=0; i<N; i++) {
             String perm = pkg.requestedPermissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(perm);
-            if (bp != null && (bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
+            if (mPermissionManager.isPermissionAppOp(perm)) {
                 ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(perm);
                 if (appOpPkgs != null) {
                     appOpPkgs.remove(pkg.packageName);
@@ -12809,23 +12106,32 @@
 
     private void updatePermissionsLPw(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
+        // have package settings, we should make note of it elsewhere [map between
+        // source package name and BasePermission] and cycle through that here. Then we
+        // define a single method on BasePermission that takes a PackageSetting, changing
+        // package name and a package.
+        // NOTE: With this approach, we also don't need to tree trees differently than
+        // 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.packageSetting == null) {
+            if (bp.getSourcePackageSetting() == null) {
                 // We may not yet have parsed the package, so just see if
                 // we still know about its settings.
-                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
+                bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
             }
-            if (bp.packageSetting == null) {
-                Slog.w(TAG, "Removing dangling permission tree: " + bp.name
-                        + " from package " + bp.sourcePackage);
+            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.sourcePackage)) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
-                    Slog.i(TAG, "Removing old permission tree: " + bp.name
-                            + " from package " + bp.sourcePackage);
+            } 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();
                 }
@@ -12834,40 +12140,28 @@
 
         // Make sure all dynamic permissions have been assigned to a package,
         // and make sure there are no dangling permissions.
-        it = mSettings.mPermissions.values().iterator();
-        while (it.hasNext()) {
-            final BasePermission bp = it.next();
-            if (bp.type == BasePermission.TYPE_DYNAMIC) {
-                if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
-                        + bp.name + " pkg=" + bp.sourcePackage
-                        + " info=" + bp.pendingInfo);
-                if (bp.packageSetting == null && bp.pendingInfo != null) {
-                    final BasePermission tree = findPermissionTreeLP(bp.name);
-                    if (tree != null && tree.perm != null) {
-                        bp.packageSetting = tree.packageSetting;
-                        bp.perm = new PackageParser.Permission(tree.perm.owner,
-                                new PermissionInfo(bp.pendingInfo));
-                        bp.perm.info.packageName = tree.perm.info.packageName;
-                        bp.perm.info.name = bp.name;
-                        bp.uid = tree.uid;
-                    }
-                }
+        final Iterator<BasePermission> permissionIter =
+                mPermissionManager.getPermissionIteratorTEMP();
+        while (permissionIter.hasNext()) {
+            final BasePermission bp = permissionIter.next();
+            if (bp.isDynamic()) {
+                bp.updateDynamicPermission(mSettings.mPermissionTrees);
             }
-            if (bp.packageSetting == null) {
+            if (bp.getSourcePackageSetting() == null) {
                 // We may not yet have parsed the package, so just see if
                 // we still know about its settings.
-                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
+                bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
             }
-            if (bp.packageSetting == null) {
-                Slog.w(TAG, "Removing dangling permission: " + bp.name
-                        + " from package " + bp.sourcePackage);
-                it.remove();
-            } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
-                    Slog.i(TAG, "Removing old permission: " + bp.name
-                            + " from package " + bp.sourcePackage);
+            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;
-                    it.remove();
+                    permissionIter.remove();
                 }
             }
         }
@@ -12936,8 +12230,9 @@
                 // the runtime ones are written only if changed. The only cases of
                 // changed runtime permissions here are promotion of an install to
                 // runtime and revocation of a runtime from a shared user.
-                changedRuntimePermissionUserIds = revokeUnusedSharedUserPermissionsLPw(
-                        ps.sharedUser, UserManagerService.getInstance().getUserIds());
+                changedRuntimePermissionUserIds =
+                        mPermissionManager.revokeUnusedSharedUserPermissions(
+                                ps.sharedUser, UserManagerService.getInstance().getUserIds());
                 if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
                     runtimePermissionsRevoked = true;
                 }
@@ -12949,7 +12244,7 @@
         final int N = pkg.requestedPermissions.size();
         for (int i=0; i<N; i++) {
             final String name = pkg.requestedPermissions.get(i);
-            final BasePermission bp = mSettings.mPermissions.get(name);
+            final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name);
             final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
                     >= Build.VERSION_CODES.M;
 
@@ -12957,7 +12252,7 @@
                 Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
             }
 
-            if (bp == null || bp.packageSetting == null) {
+            if (bp == null || bp.getSourcePackageSetting() == null) {
                 if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
                     if (DEBUG_PERMISSIONS) {
                         Slog.i(TAG, "Unknown permission " + name
@@ -12971,7 +12266,7 @@
             // Limit ephemeral apps to ephemeral allowed permissions.
             if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
                 if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying non-ephemeral permission " + bp.name + " for package "
+                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package "
                             + pkg.packageName);
                 }
                 continue;
@@ -12979,64 +12274,57 @@
 
             if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
                 if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying runtime-only permission " + bp.name + " for package "
+                    Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package "
                             + pkg.packageName);
                 }
                 continue;
             }
 
-            final String perm = bp.name;
+            final String perm = bp.getName();
             boolean allowedSig = false;
             int grant = GRANT_DENIED;
 
             // Keep track of app op permissions.
-            if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
-                ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name);
+            if (bp.isAppOp()) {
+                ArraySet<String> pkgs = mAppOpPermissionPackages.get(perm);
                 if (pkgs == null) {
                     pkgs = new ArraySet<>();
-                    mAppOpPermissionPackages.put(bp.name, pkgs);
+                    mAppOpPermissionPackages.put(perm, pkgs);
                 }
                 pkgs.add(pkg.packageName);
             }
 
-            final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
-            switch (level) {
-                case PermissionInfo.PROTECTION_NORMAL: {
-                    // For all apps normal permissions are install time ones.
+            if (bp.isNormal()) {
+                // For all apps normal permissions are install time ones.
+                grant = GRANT_INSTALL;
+            } else if (bp.isRuntime()) {
+                // If a permission review is required for legacy apps we represent
+                // their permissions as always granted runtime ones since we need
+                // to keep the review required permission flag per user while an
+                // install permission's state is shared across all users.
+                if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
+                    // For legacy apps dangerous permissions are install time ones.
                     grant = GRANT_INSTALL;
-                } break;
-
-                case PermissionInfo.PROTECTION_DANGEROUS: {
-                    // If a permission review is required for legacy apps we represent
-                    // their permissions as always granted runtime ones since we need
-                    // to keep the review required permission flag per user while an
-                    // install permission's state is shared across all users.
-                    if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
-                        // For legacy apps dangerous permissions are install time ones.
-                        grant = GRANT_INSTALL;
-                    } else if (origPermissions.hasInstallPermission(bp.name)) {
-                        // For legacy apps that became modern, install becomes runtime.
-                        grant = GRANT_UPGRADE;
-                    } else if (mPromoteSystemApps
-                            && isSystemApp(ps)
-                            && mExistingSystemPackages.contains(ps.name)) {
-                        // For legacy system apps, install becomes runtime.
-                        // We cannot check hasInstallPermission() for system apps since those
-                        // permissions were granted implicitly and not persisted pre-M.
-                        grant = GRANT_UPGRADE;
-                    } else {
-                        // For modern apps keep runtime permissions unchanged.
-                        grant = GRANT_RUNTIME;
-                    }
-                } break;
-
-                case PermissionInfo.PROTECTION_SIGNATURE: {
-                    // For all apps signature permissions are install time ones.
-                    allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
-                    if (allowedSig) {
-                        grant = GRANT_INSTALL;
-                    }
-                } break;
+                } else if (origPermissions.hasInstallPermission(bp.getName())) {
+                    // For legacy apps that became modern, install becomes runtime.
+                    grant = GRANT_UPGRADE;
+                } else if (mPromoteSystemApps
+                        && isSystemApp(ps)
+                        && mExistingSystemPackages.contains(ps.name)) {
+                    // For legacy system apps, install becomes runtime.
+                    // We cannot check hasInstallPermission() for system apps since those
+                    // permissions were granted implicitly and not persisted pre-M.
+                    grant = GRANT_UPGRADE;
+                } else {
+                    // For modern apps keep runtime permissions unchanged.
+                    grant = GRANT_RUNTIME;
+                }
+            } else if (bp.isSignature()) {
+                // For all apps signature permissions are install time ones.
+                allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+                if (allowedSig) {
+                    grant = GRANT_INSTALL;
+                }
             }
 
             if (DEBUG_PERMISSIONS) {
@@ -13065,7 +12353,7 @@
                         // for legacy apps
                         for (int userId : UserManagerService.getInstance().getUserIds()) {
                             if (origPermissions.getRuntimePermissionState(
-                                    bp.name, userId) != null) {
+                                    perm, userId) != null) {
                                 // Revoke the runtime permission and clear the flags.
                                 origPermissions.revokeRuntimePermission(bp, userId);
                                 origPermissions.updatePermissionFlags(bp, userId,
@@ -13086,10 +12374,10 @@
                         // Grant previously granted runtime permissions.
                         for (int userId : UserManagerService.getInstance().getUserIds()) {
                             PermissionState permissionState = origPermissions
-                                    .getRuntimePermissionState(bp.name, userId);
+                                    .getRuntimePermissionState(perm, userId);
                             int flags = permissionState != null
                                     ? permissionState.getFlags() : 0;
-                            if (origPermissions.hasRuntimePermission(bp.name, userId)) {
+                            if (origPermissions.hasRuntimePermission(perm, userId)) {
                                 // Don't propagate the permission in a permission review mode if
                                 // the former was revoked, i.e. marked to not propagate on upgrade.
                                 // Note that in a permission review mode install permissions are
@@ -13132,7 +12420,7 @@
                                 // permissions as these are the only ones the platform knows
                                 // how to disable the API to simulate revocation as legacy
                                 // apps don't expect to run with revoked permissions.
-                                if (PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage)) {
+                                if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
                                     if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
                                         flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
                                         // We changed the flags, hence have to write.
@@ -13155,7 +12443,7 @@
                     case GRANT_UPGRADE: {
                         // Grant runtime permissions for a previously held install permission.
                         PermissionState permissionState = origPermissions
-                                .getInstallPermissionState(bp.name);
+                                .getInstallPermissionState(perm);
                         final int flags = permissionState != null ? permissionState.getFlags() : 0;
 
                         if (origPermissions.revokeInstallPermission(bp)
@@ -13203,10 +12491,10 @@
                     changedInstallPermission = true;
                     Slog.i(TAG, "Un-granting permission " + perm
                             + " from package " + pkg.packageName
-                            + " (protectionLevel=" + bp.protectionLevel
+                            + " (protectionLevel=" + bp.getProtectionLevel()
                             + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
                             + ")");
-                } else if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) == 0) {
+                } else if (bp.isAppOp()) {
                     // Don't print warning for app op permissions, since it is fine for them
                     // not to be granted, there is a UI for the user to decide.
                     if (DEBUG_PERMISSIONS
@@ -13214,7 +12502,7 @@
                                     || packageOfInterest.equals(pkg.packageName))) {
                         Slog.i(TAG, "Not granting permission " + perm
                                 + " to package " + pkg.packageName
-                                + " (protectionLevel=" + bp.protectionLevel
+                                + " (protectionLevel=" + bp.getProtectionLevel()
                                 + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
                                 + ")");
                     }
@@ -13274,13 +12562,11 @@
 
     private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
             BasePermission bp, PermissionsState origPermissions) {
-        boolean oemPermission = (bp.protectionLevel
-                & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
-        boolean privilegedPermission = (bp.protectionLevel
-                & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
+        boolean oemPermission = bp.isOEM();
+        boolean privilegedPermission = bp.isPrivileged();
         boolean privappPermissionsDisable =
                 RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
-        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage);
+        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
         boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
         if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp()
                 && !platformPackage && platformPermission) {
@@ -13309,7 +12595,7 @@
             }
         }
         boolean allowed = (compareSignatures(
-                bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
+                bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH)
                 || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH);
@@ -13386,39 +12672,37 @@
             }
         }
         if (!allowed) {
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_PRE23) != 0
+            if (!allowed
+                    && bp.isPre23()
                     && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                 // If this was a previously normal/dangerous permission that got moved
                 // to a system permission as part of the runtime permission redesign, then
                 // we still want to blindly grant it to old apps.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0
+            if (!allowed && bp.isInstaller()
                     && pkg.packageName.equals(mRequiredInstallerPackage)) {
                 // If this permission is to be granted to the system installer and
                 // this app is an installer, then it gets the permission.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0
+            if (!allowed && bp.isVerifier()
                     && pkg.packageName.equals(mRequiredVerifierPackage)) {
                 // If this permission is to be granted to the system verifier and
                 // this app is a verifier, then it gets the permission.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0
+            if (!allowed && bp.isPreInstalled()
                     && isSystemApp(pkg)) {
                 // Any pre-installed system app is allowed to get this permission.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+            if (!allowed && bp.isDevelopment()) {
                 // For development permissions, a development permission
                 // is granted only if it was already granted.
                 allowed = origPermissions.hasInstallPermission(perm);
             }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0
+            if (!allowed && bp.isSetup()
                     && pkg.packageName.equals(mSetupWizardPackage)) {
                 // If this permission is to be granted to the system setup wizard and
                 // this app is a setup wizard, then it gets the permission.
@@ -14719,7 +14003,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */, "installPackageAsUser");
 
         if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -14847,7 +14131,7 @@
         return installReason;
     }
 
-    void installStage(String packageName, File stagedDir, String stagedCid,
+    void installStage(String packageName, File stagedDir,
             IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
             String installerPackageName, int installerUid, UserHandle user,
             Certificate[][] certificates) {
@@ -14860,12 +14144,7 @@
                 sessionParams.originatingUri, sessionParams.referrerUri,
                 sessionParams.originatingUid, installerUid);
 
-        final OriginInfo origin;
-        if (stagedDir != null) {
-            origin = OriginInfo.fromStagedFile(stagedDir);
-        } else {
-            origin = OriginInfo.fromStagedContainer(stagedCid);
-        }
+        final OriginInfo origin = OriginInfo.fromStagedFile(stagedDir);
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         final int installReason = fixUpInstallReason(installerPackageName, installerUid,
@@ -14963,7 +14242,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         PackageSetting pkgSetting;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setApplicationHiddenSetting for user " + userId);
 
@@ -15065,7 +14344,7 @@
     public boolean getApplicationHiddenSettingAsUser(String packageName, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getApplicationHidden for user " + userId);
         PackageSetting ps;
@@ -15097,7 +14376,7 @@
                 null);
         PackageSetting pkgSetting;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "installExistingPackage for user " + userId);
         if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -15202,7 +14481,7 @@
             int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setPackagesSuspended for user " + userId);
 
@@ -15263,7 +14542,7 @@
     @Override
     public boolean isPackageSuspendedForUser(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "isPackageSuspendedForUser for user " + userId);
         synchronized (mPackages) {
@@ -15710,7 +14989,7 @@
         synchronized (mPackages) {
             boolean result = mSettings.setDefaultBrowserPackageNameLPw(packageName, userId);
             if (packageName != null) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowserLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowser(
                         packageName, userId);
             }
             return result;
@@ -16065,7 +15344,6 @@
          * file, or a cluster directory. This location may be untrusted.
          */
         final File file;
-        final String cid;
 
         /**
          * Flag indicating that {@link #file} or {@link #cid} has already been
@@ -16084,35 +15362,27 @@
         final File resolvedFile;
 
         static OriginInfo fromNothing() {
-            return new OriginInfo(null, null, false, false);
+            return new OriginInfo(null, false, false);
         }
 
         static OriginInfo fromUntrustedFile(File file) {
-            return new OriginInfo(file, null, false, false);
+            return new OriginInfo(file, false, false);
         }
 
         static OriginInfo fromExistingFile(File file) {
-            return new OriginInfo(file, null, false, true);
+            return new OriginInfo(file, false, true);
         }
 
         static OriginInfo fromStagedFile(File file) {
-            return new OriginInfo(file, null, true, false);
+            return new OriginInfo(file, true, false);
         }
 
-        static OriginInfo fromStagedContainer(String cid) {
-            return new OriginInfo(null, cid, true, false);
-        }
-
-        private OriginInfo(File file, String cid, boolean staged, boolean existing) {
+        private OriginInfo(File file, boolean staged, boolean existing) {
             this.file = file;
-            this.cid = cid;
             this.staged = staged;
             this.existing = existing;
 
-            if (cid != null) {
-                resolvedPath = PackageHelper.getSdDir(cid);
-                resolvedFile = new File(resolvedPath);
-            } else if (file != null) {
+            if (file != null) {
                 resolvedPath = file.getAbsolutePath();
                 resolvedFile = file;
             } else {
@@ -16205,7 +15475,7 @@
         @Override
         public String toString() {
             return "InstallParams{" + Integer.toHexString(System.identityHashCode(this))
-                    + " file=" + origin.file + " cid=" + origin.cid + "}";
+                    + " file=" + origin.file + "}";
         }
 
         private int installLocationPolicy(PackageInfoLite pkgLite) {
@@ -16316,9 +15586,6 @@
                 if (origin.file != null) {
                     installFlags |= PackageManager.INSTALL_INTERNAL;
                     installFlags &= ~PackageManager.INSTALL_EXTERNAL;
-                } else if (origin.cid != null) {
-                    installFlags |= PackageManager.INSTALL_EXTERNAL;
-                    installFlags &= ~PackageManager.INSTALL_INTERNAL;
                 } else {
                     throw new IllegalStateException("Invalid stage location");
                 }
@@ -16356,7 +15623,7 @@
                             Environment.getDataDirectory());
 
                     final long sizeBytes = mContainerService.calculateInstalledSize(
-                            origin.resolvedPath, isForwardLocked(), packageAbiOverride);
+                            origin.resolvedPath, packageAbiOverride);
 
                     try {
                         mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
@@ -16593,43 +15860,11 @@
             mArgs = createInstallArgs(this);
             mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
         }
-
-        public boolean isForwardLocked() {
-            return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
-        }
-    }
-
-    /**
-     * Used during creation of InstallArgs
-     *
-     * @param installFlags package installation flags
-     * @return true if should be installed on external storage
-     */
-    private static boolean installOnExternalAsec(int installFlags) {
-        if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
-            return false;
-        }
-        if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Used during creation of InstallArgs
-     *
-     * @param installFlags package installation flags
-     * @return true if should be installed as forward locked
-     */
-    private static boolean installForwardLocked(int installFlags) {
-        return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
     }
 
     private InstallArgs createInstallArgs(InstallParams params) {
         if (params.move != null) {
             return new MoveInstallArgs(params);
-        } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
-            return new AsecInstallArgs(params);
         } else {
             return new FileInstallArgs(params);
         }
@@ -16641,27 +15876,7 @@
      */
     private InstallArgs createInstallArgsForExisting(int installFlags, String codePath,
             String resourcePath, String[] instructionSets) {
-        final boolean isInAsec;
-        if (installOnExternalAsec(installFlags)) {
-            /* Apps on SD card are always in ASEC containers. */
-            isInAsec = true;
-        } else if (installForwardLocked(installFlags)
-                && !codePath.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath())) {
-            /*
-             * Forward-locked apps are only in ASEC containers if they're the
-             * new style
-             */
-            isInAsec = true;
-        } else {
-            isInAsec = false;
-        }
-
-        if (isInAsec) {
-            return new AsecInstallArgs(codePath, instructionSets,
-                    installOnExternalAsec(installFlags), installForwardLocked(installFlags));
-        } else {
-            return new FileInstallArgs(codePath, resourcePath, instructionSets);
-        }
+        return new FileInstallArgs(codePath, resourcePath, instructionSets);
     }
 
     static abstract class InstallArgs {
@@ -16995,11 +16210,6 @@
         }
     }
 
-    private boolean isAsecExternal(String cid) {
-        final String asecPath = PackageHelper.getSdFilesystem(cid);
-        return !asecPath.startsWith(mAsecInternalPath);
-    }
-
     private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws
             PackageManagerException {
         if (copyRet < 0) {
@@ -17022,308 +16232,6 @@
     }
 
     /**
-     * Logic to handle installation of ASEC applications, including copying and
-     * renaming logic.
-     */
-    class AsecInstallArgs extends InstallArgs {
-        static final String RES_FILE_NAME = "pkg.apk";
-        static final String PUBLIC_RES_FILE_NAME = "res.zip";
-
-        String cid;
-        String packagePath;
-        String resourcePath;
-
-        /** New install */
-        AsecInstallArgs(InstallParams params) {
-            super(params.origin, params.move, params.observer, params.installFlags,
-                    params.installerPackageName, params.volumeUuid,
-                    params.getUser(), null /* instruction sets */, params.packageAbiOverride,
-                    params.grantedRuntimePermissions,
-                    params.traceMethod, params.traceCookie, params.certificates,
-                    params.installReason);
-        }
-
-        /** Existing install */
-        AsecInstallArgs(String fullCodePath, String[] instructionSets,
-                        boolean isExternal, boolean isForwardLocked) {
-            super(OriginInfo.fromNothing(), null, null, (isExternal ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
-                    instructionSets, null, null, null, 0, null /*certificates*/,
-                    PackageManager.INSTALL_REASON_UNKNOWN);
-            // Hackily pretend we're still looking at a full code path
-            if (!fullCodePath.endsWith(RES_FILE_NAME)) {
-                fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath();
-            }
-
-            // Extract cid from fullCodePath
-            int eidx = fullCodePath.lastIndexOf("/");
-            String subStr1 = fullCodePath.substring(0, eidx);
-            int sidx = subStr1.lastIndexOf("/");
-            cid = subStr1.substring(sidx+1, eidx);
-            setMountPath(subStr1);
-        }
-
-        AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) {
-            super(OriginInfo.fromNothing(), null, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
-                    instructionSets, null, null, null, 0, null /*certificates*/,
-                    PackageManager.INSTALL_REASON_UNKNOWN);
-            this.cid = cid;
-            setMountPath(PackageHelper.getSdDir(cid));
-        }
-
-        void createCopyFile() {
-            cid = mInstallerService.allocateExternalStageCidLegacy();
-        }
-
-        int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
-            if (origin.staged && origin.cid != null) {
-                if (DEBUG_INSTALL) Slog.d(TAG, origin.cid + " already staged; skipping copy");
-                cid = origin.cid;
-                setMountPath(PackageHelper.getSdDir(cid));
-                return PackageManager.INSTALL_SUCCEEDED;
-            }
-
-            if (temp) {
-                createCopyFile();
-            } else {
-                /*
-                 * Pre-emptively destroy the container since it's destroyed if
-                 * copying fails due to it existing anyway.
-                 */
-                PackageHelper.destroySdDir(cid);
-            }
-
-            final String newMountPath = imcs.copyPackageToContainer(
-                    origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternalAsec(),
-                    isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */));
-
-            if (newMountPath != null) {
-                setMountPath(newMountPath);
-                return PackageManager.INSTALL_SUCCEEDED;
-            } else {
-                return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-            }
-        }
-
-        @Override
-        String getCodePath() {
-            return packagePath;
-        }
-
-        @Override
-        String getResourcePath() {
-            return resourcePath;
-        }
-
-        int doPreInstall(int status) {
-            if (status != PackageManager.INSTALL_SUCCEEDED) {
-                // Destroy container
-                PackageHelper.destroySdDir(cid);
-            } else {
-                boolean mounted = PackageHelper.isContainerMounted(cid);
-                if (!mounted) {
-                    String newMountPath = PackageHelper.mountSdDir(cid, getEncryptKey(),
-                            Process.SYSTEM_UID);
-                    if (newMountPath != null) {
-                        setMountPath(newMountPath);
-                    } else {
-                        return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                    }
-                }
-            }
-            return status;
-        }
-
-        boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) {
-            String newCacheId = getNextCodePath(oldCodePath, pkg.packageName, "/" + RES_FILE_NAME);
-            String newMountPath = null;
-            if (PackageHelper.isContainerMounted(cid)) {
-                // Unmount the container
-                if (!PackageHelper.unMountSdDir(cid)) {
-                    Slog.i(TAG, "Failed to unmount " + cid + " before renaming");
-                    return false;
-                }
-            }
-            if (!PackageHelper.renameSdDir(cid, newCacheId)) {
-                Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId +
-                        " which might be stale. Will try to clean up.");
-                // Clean up the stale container and proceed to recreate.
-                if (!PackageHelper.destroySdDir(newCacheId)) {
-                    Slog.e(TAG, "Very strange. Cannot clean up stale container " + newCacheId);
-                    return false;
-                }
-                // Successfully cleaned up stale container. Try to rename again.
-                if (!PackageHelper.renameSdDir(cid, newCacheId)) {
-                    Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId
-                            + " inspite of cleaning it up.");
-                    return false;
-                }
-            }
-            if (!PackageHelper.isContainerMounted(newCacheId)) {
-                Slog.w(TAG, "Mounting container " + newCacheId);
-                newMountPath = PackageHelper.mountSdDir(newCacheId,
-                        getEncryptKey(), Process.SYSTEM_UID);
-            } else {
-                newMountPath = PackageHelper.getSdDir(newCacheId);
-            }
-            if (newMountPath == null) {
-                Slog.w(TAG, "Failed to get cache path for  " + newCacheId);
-                return false;
-            }
-            Log.i(TAG, "Succesfully renamed " + cid +
-                    " to " + newCacheId +
-                    " at new path: " + newMountPath);
-            cid = newCacheId;
-
-            final File beforeCodeFile = new File(packagePath);
-            setMountPath(newMountPath);
-            final File afterCodeFile = new File(packagePath);
-
-            // Reflect the rename in scanned details
-            pkg.setCodePath(afterCodeFile.getAbsolutePath());
-            pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
-                    afterCodeFile, pkg.baseCodePath));
-            pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
-                    afterCodeFile, pkg.splitCodePaths));
-
-            // Reflect the rename in app info
-            pkg.setApplicationVolumeUuid(pkg.volumeUuid);
-            pkg.setApplicationInfoCodePath(pkg.codePath);
-            pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
-            pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
-            pkg.setApplicationInfoResourcePath(pkg.codePath);
-            pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
-            pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
-
-            return true;
-        }
-
-        private void setMountPath(String mountPath) {
-            final File mountFile = new File(mountPath);
-
-            final File monolithicFile = new File(mountFile, RES_FILE_NAME);
-            if (monolithicFile.exists()) {
-                packagePath = monolithicFile.getAbsolutePath();
-                if (isFwdLocked()) {
-                    resourcePath = new File(mountFile, PUBLIC_RES_FILE_NAME).getAbsolutePath();
-                } else {
-                    resourcePath = packagePath;
-                }
-            } else {
-                packagePath = mountFile.getAbsolutePath();
-                resourcePath = packagePath;
-            }
-        }
-
-        int doPostInstall(int status, int uid) {
-            if (status != PackageManager.INSTALL_SUCCEEDED) {
-                cleanUp();
-            } else {
-                final int groupOwner;
-                final String protectedFile;
-                if (isFwdLocked()) {
-                    groupOwner = UserHandle.getSharedAppGid(uid);
-                    protectedFile = RES_FILE_NAME;
-                } else {
-                    groupOwner = -1;
-                    protectedFile = null;
-                }
-
-                if (uid < Process.FIRST_APPLICATION_UID
-                        || !PackageHelper.fixSdPermissions(cid, groupOwner, protectedFile)) {
-                    Slog.e(TAG, "Failed to finalize " + cid);
-                    PackageHelper.destroySdDir(cid);
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-
-                boolean mounted = PackageHelper.isContainerMounted(cid);
-                if (!mounted) {
-                    PackageHelper.mountSdDir(cid, getEncryptKey(), Process.myUid());
-                }
-            }
-            return status;
-        }
-
-        private void cleanUp() {
-            if (DEBUG_SD_INSTALL) Slog.i(TAG, "cleanUp");
-
-            // Destroy secure container
-            PackageHelper.destroySdDir(cid);
-        }
-
-        private List<String> getAllCodePaths() {
-            final File codeFile = new File(getCodePath());
-            if (codeFile != null && codeFile.exists()) {
-                try {
-                    final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0);
-                    return pkg.getAllCodePaths();
-                } catch (PackageParserException e) {
-                    // Ignored; we tried our best
-                }
-            }
-            return Collections.EMPTY_LIST;
-        }
-
-        void cleanUpResourcesLI() {
-            // Enumerate all code paths before deleting
-            cleanUpResourcesLI(getAllCodePaths());
-        }
-
-        private void cleanUpResourcesLI(List<String> allCodePaths) {
-            cleanUp();
-            removeDexFiles(allCodePaths, instructionSets);
-        }
-
-        String getPackageName() {
-            return getAsecPackageName(cid);
-        }
-
-        boolean doPostDeleteLI(boolean delete) {
-            if (DEBUG_SD_INSTALL) Slog.i(TAG, "doPostDeleteLI() del=" + delete);
-            final List<String> allCodePaths = getAllCodePaths();
-            boolean mounted = PackageHelper.isContainerMounted(cid);
-            if (mounted) {
-                // Unmount first
-                if (PackageHelper.unMountSdDir(cid)) {
-                    mounted = false;
-                }
-            }
-            if (!mounted && delete) {
-                cleanUpResourcesLI(allCodePaths);
-            }
-            return !mounted;
-        }
-
-        @Override
-        int doPreCopy() {
-            if (isFwdLocked()) {
-                if (!PackageHelper.fixSdPermissions(cid, getPackageUid(DEFAULT_CONTAINER_PACKAGE,
-                        MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM), RES_FILE_NAME)) {
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-            }
-
-            return PackageManager.INSTALL_SUCCEEDED;
-        }
-
-        @Override
-        int doPostCopy(int uid) {
-            if (isFwdLocked()) {
-                if (uid < Process.FIRST_APPLICATION_UID
-                        || !PackageHelper.fixSdPermissions(cid, UserHandle.getSharedAppGid(uid),
-                                RES_FILE_NAME)) {
-                    Slog.e(TAG, "Failed to finalize " + cid);
-                    PackageHelper.destroySdDir(cid);
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-            }
-
-            return PackageManager.INSTALL_SUCCEEDED;
-        }
-    }
-
-    /**
      * Logic to handle movement of existing installed applications.
      */
     class MoveInstallArgs extends InstallArgs {
@@ -17627,9 +16535,9 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    private boolean shouldCheckUpgradeKeySetLP(PackageSetting oldPs, int scanFlags) {
+    private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
         // Can't rotate keys during boot or if sharedUser.
-        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.sharedUser != null
+        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
                 || !oldPs.keySetData.isUsingUpgradeKeySets()) {
             return false;
         }
@@ -17649,7 +16557,7 @@
         return true;
     }
 
-    private boolean checkUpgradeKeySetLP(PackageSetting oldPS, PackageParser.Package newPkg) {
+    private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
         // Upgrade keysets are being used.  Determine if new package has a superset of the
         // required keys.
         long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
@@ -18225,66 +17133,6 @@
         }
     }
 
-    private int[] revokeUnusedSharedUserPermissionsLPw(SharedUserSetting su, int[] allUserIds) {
-        // Collect all used permissions in the UID
-        ArraySet<String> usedPermissions = new ArraySet<>();
-        final int packageCount = su.packages.size();
-        for (int i = 0; i < packageCount; i++) {
-            PackageSetting ps = su.packages.valueAt(i);
-            if (ps.pkg == null) {
-                continue;
-            }
-            final int requestedPermCount = ps.pkg.requestedPermissions.size();
-            for (int j = 0; j < requestedPermCount; j++) {
-                String permission = ps.pkg.requestedPermissions.get(j);
-                BasePermission bp = mSettings.mPermissions.get(permission);
-                if (bp != null) {
-                    usedPermissions.add(permission);
-                }
-            }
-        }
-
-        PermissionsState permissionsState = su.getPermissionsState();
-        // Prune install permissions
-        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
-        final int installPermCount = installPermStates.size();
-        for (int i = installPermCount - 1; i >= 0;  i--) {
-            PermissionState permissionState = installPermStates.get(i);
-            if (!usedPermissions.contains(permissionState.getName())) {
-                BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
-                if (bp != null) {
-                    permissionsState.revokeInstallPermission(bp);
-                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                            PackageManager.MASK_PERMISSION_FLAGS, 0);
-                }
-            }
-        }
-
-        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
-
-        // Prune runtime permissions
-        for (int userId : allUserIds) {
-            List<PermissionState> runtimePermStates = permissionsState
-                    .getRuntimePermissionStates(userId);
-            final int runtimePermCount = runtimePermStates.size();
-            for (int i = runtimePermCount - 1; i >= 0; i--) {
-                PermissionState permissionState = runtimePermStates.get(i);
-                if (!usedPermissions.contains(permissionState.getName())) {
-                    BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
-                    if (bp != null) {
-                        permissionsState.revokeRuntimePermission(bp, userId);
-                        permissionsState.updatePermissionFlags(bp, userId,
-                                PackageManager.MASK_PERMISSION_FLAGS, 0);
-                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
-                                runtimePermissionChangedUserIds, userId);
-                    }
-                }
-            }
-        }
-
-        return runtimePermissionChangedUserIds;
-    }
-
     private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
             int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) {
         // Update the parent package setting
@@ -18685,8 +17533,9 @@
 
             int N = pkg.permissions.size();
             for (int i = N-1; i >= 0; i--) {
-                PackageParser.Permission perm = pkg.permissions.get(i);
-                BasePermission bp = mSettings.mPermissions.get(perm.info.name);
+                final PackageParser.Permission perm = pkg.permissions.get(i);
+                final BasePermission bp =
+                        (BasePermission) mPermissionManager.getPermissionTEMP(perm.info.name);
 
                 // Don't allow anyone but the system to define ephemeral permissions.
                 if ((perm.info.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
@@ -18703,25 +17552,26 @@
                     // also includes the "updating the same package" case, of course.
                     // "updating same package" could also involve key-rotation.
                     final boolean sigsOk;
-                    if (bp.sourcePackage.equals(pkg.packageName)
-                            && (bp.packageSetting instanceof PackageSetting)
-                            && (shouldCheckUpgradeKeySetLP((PackageSetting) bp.packageSetting,
+                    final String sourcePackageName = bp.getSourcePackageName();
+                    final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+                    if (sourcePackageName.equals(pkg.packageName)
+                            && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
                                     scanFlags))) {
-                        sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
+                        sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
                     } else {
-                        sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures,
+                        sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
                                 pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
                     }
                     if (!sigsOk) {
                         // If the owning package is the system itself, we log but allow
                         // install to proceed; we fail the install on all other permission
                         // redefinitions.
-                        if (!bp.sourcePackage.equals("android")) {
+                        if (!sourcePackageName.equals("android")) {
                             res.setError(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
                                     + pkg.packageName + " attempting to redeclare permission "
-                                    + perm.info.name + " already owned by " + bp.sourcePackage);
+                                    + perm.info.name + " already owned by " + sourcePackageName);
                             res.origPermission = perm.info.name;
-                            res.origPackage = bp.sourcePackage;
+                            res.origPackage = sourcePackageName;
                             return;
                         } else {
                             Slog.w(TAG, "Package " + pkg.packageName
@@ -18740,7 +17590,7 @@
                                 Slog.w(TAG, "Package " + pkg.packageName + " trying to change a "
                                         + "non-runtime permission " + perm.info.name
                                         + " to runtime; keeping old protection level");
-                                perm.info.protectionLevel = bp.protectionLevel;
+                                perm.info.protectionLevel = bp.getProtectionLevel();
                             }
                         }
                     }
@@ -18855,7 +17705,13 @@
         // TODO: Layering violation
         BackgroundDexOptService.notifyPackageChanged(pkg.packageName);
 
-        startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+        if (!instantApp) {
+            startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+        } else {
+            if (DEBUG_DOMAIN_VERIFICATION) {
+                Slog.d(TAG, "Not verifying instant app install for app links: " + pkgName);
+            }
+        }
 
         try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
                 "installPackageLI")) {
@@ -20440,7 +19296,7 @@
                 android.Manifest.permission.CLEAR_APP_USER_DATA, null);
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "clear application data");
 
         final PackageSetting ps = mSettings.getPackageLPr(packageName);
@@ -20579,9 +19435,9 @@
 
         final int permissionCount = ps.pkg.requestedPermissions.size();
         for (int i = 0; i < permissionCount; i++) {
-            String permission = ps.pkg.requestedPermissions.get(i);
-
-            BasePermission bp = mSettings.mPermissions.get(permission);
+            final String permName = ps.pkg.requestedPermissions.get(i);
+            final BasePermission bp =
+                    (BasePermission) mPermissionManager.getPermissionTEMP(permName);
             if (bp == null) {
                 continue;
             }
@@ -20593,7 +19449,7 @@
                 for (int j = 0; j < packageCount; j++) {
                     PackageSetting pkg = ps.sharedUser.packages.valueAt(j);
                     if (pkg.pkg != null && !pkg.pkg.packageName.equals(ps.pkg.packageName)
-                            && pkg.pkg.requestedPermissions.contains(permission)) {
+                            && pkg.pkg.requestedPermissions.contains(permName)) {
                         used = true;
                         break;
                     }
@@ -20603,13 +19459,13 @@
                 }
             }
 
-            PermissionsState permissionsState = ps.getPermissionsState();
+            final PermissionsState permissionsState = ps.getPermissionsState();
 
-            final int oldFlags = permissionsState.getPermissionFlags(bp.name, userId);
+            final int oldFlags = permissionsState.getPermissionFlags(permName, userId);
 
             // Always clear the user settable flags.
-            final boolean hasInstallState = permissionsState.getInstallPermissionState(
-                    bp.name) != null;
+            final boolean hasInstallState =
+                    permissionsState.getInstallPermissionState(permName) != null;
             // If permission review is enabled and this is a legacy app, mark the
             // permission as requiring a review as this is the initial state.
             int flags = 0;
@@ -20709,7 +19565,7 @@
         final int callingUid = Binder.getCallingUid();
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.DELETE_CACHE_FILES, null);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 /* requireFullPermission= */ true, /* checkShell= */ false,
                 "delete application cache files");
         final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
@@ -20829,7 +19685,7 @@
             String opname) {
         // writer
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "add preferred activity");
         if (filter.countActions() == 0) {
             Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
@@ -20894,7 +19750,7 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "replace preferred activity");
         synchronized (mPackages) {
@@ -21571,8 +20427,11 @@
                     newFlagSet |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
                 }
                 if (DEBUG_BACKUP) {
-                    Slog.v(TAG, "  + Restoring grant: pkg=" + pkgName + " perm=" + permName
-                            + " granted=" + isGranted + " bits=0x" + Integer.toHexString(newFlagSet));
+                    Slog.v(TAG, "  + Restoring grant:"
+                            + " pkg=" + pkgName
+                            + " perm=" + permName
+                            + " granted=" + isGranted
+                            + " bits=0x" + Integer.toHexString(newFlagSet));
                 }
                 final PackageSetting ps = mSettings.mPackages.get(pkgName);
                 if (ps != null) {
@@ -21581,13 +20440,15 @@
                         Slog.v(TAG, "        + already installed; applying");
                     }
                     PermissionsState perms = ps.getPermissionsState();
-                    BasePermission bp = mSettings.mPermissions.get(permName);
+                    BasePermission bp =
+                            (BasePermission) mPermissionManager.getPermissionTEMP(permName);
                     if (bp != null) {
                         if (isGranted) {
                             perms.grantRuntimePermission(bp, userId);
                         }
                         if (newFlagSet != 0) {
-                            perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
+                            perms.updatePermissionFlags(
+                                    bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
                         }
                     }
                 } else {
@@ -21616,7 +20477,8 @@
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         int callingUid = Binder.getCallingUid();
         enforceOwnerRights(ownerPackage, callingUid);
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+        PackageManagerServiceUtils.enforceShellRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
         if (intentFilter.countActions() == 0) {
             Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
             return;
@@ -21647,7 +20509,8 @@
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         final int callingUid = Binder.getCallingUid();
         enforceOwnerRights(ownerPackage, callingUid);
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+        PackageManagerServiceUtils.enforceShellRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
         synchronized (mPackages) {
             CrossProfileIntentResolver resolver =
                     mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
@@ -21877,7 +20740,7 @@
             permission = mContext.checkCallingOrSelfPermission(
                     android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         }
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, true /* checkShell */, "set enabled");
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
         boolean sendNow = false;
@@ -22166,7 +21029,7 @@
         if (!sUserManager.exists(userId)) {
             return;
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
                 false /* checkShell */, "flushPackageRestrictions");
         synchronized (mPackages) {
             mSettings.writePackageRestrictionsLPr(userId);
@@ -22208,7 +21071,7 @@
         final int permission = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */, "stop package");
         // writer
         synchronized (mPackages) {
@@ -22248,7 +21111,7 @@
     public int getApplicationEnabledSetting(String packageName, int userId) {
         if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get enabled");
         // reader
         synchronized (mPackages) {
@@ -22263,7 +21126,7 @@
     public int getComponentEnabledSetting(ComponentName component, int userId) {
         if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getComponentEnabled");
         synchronized (mPackages) {
             if (filterAppAccessLPr(mSettings.getPackageLPr(component.getPackageName()), callingUid,
@@ -22361,7 +21224,7 @@
 
         // If we upgraded grant all default permissions before kicking off.
         for (int userId : grantPermissionsUserIds) {
-            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
+            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
         }
 
         if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22465,85 +21328,6 @@
         return buf.toString();
     }
 
-    static class DumpState {
-        public static final int DUMP_LIBS = 1 << 0;
-        public static final int DUMP_FEATURES = 1 << 1;
-        public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
-        public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
-        public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
-        public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
-        public static final int DUMP_PERMISSIONS = 1 << 6;
-        public static final int DUMP_PACKAGES = 1 << 7;
-        public static final int DUMP_SHARED_USERS = 1 << 8;
-        public static final int DUMP_MESSAGES = 1 << 9;
-        public static final int DUMP_PROVIDERS = 1 << 10;
-        public static final int DUMP_VERIFIERS = 1 << 11;
-        public static final int DUMP_PREFERRED = 1 << 12;
-        public static final int DUMP_PREFERRED_XML = 1 << 13;
-        public static final int DUMP_KEYSETS = 1 << 14;
-        public static final int DUMP_VERSION = 1 << 15;
-        public static final int DUMP_INSTALLS = 1 << 16;
-        public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
-        public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
-        public static final int DUMP_FROZEN = 1 << 19;
-        public static final int DUMP_DEXOPT = 1 << 20;
-        public static final int DUMP_COMPILER_STATS = 1 << 21;
-        public static final int DUMP_CHANGES = 1 << 22;
-        public static final int DUMP_VOLUMES = 1 << 23;
-
-        public static final int OPTION_SHOW_FILTERS = 1 << 0;
-
-        private int mTypes;
-
-        private int mOptions;
-
-        private boolean mTitlePrinted;
-
-        private SharedUserSetting mSharedUser;
-
-        public boolean isDumping(int type) {
-            if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
-                return true;
-            }
-
-            return (mTypes & type) != 0;
-        }
-
-        public void setDump(int type) {
-            mTypes |= type;
-        }
-
-        public boolean isOptionEnabled(int option) {
-            return (mOptions & option) != 0;
-        }
-
-        public void setOptionEnabled(int option) {
-            mOptions |= option;
-        }
-
-        public boolean onTitlePrinted() {
-            final boolean printed = mTitlePrinted;
-            mTitlePrinted = true;
-            return printed;
-        }
-
-        public boolean getTitlePrinted() {
-            return mTitlePrinted;
-        }
-
-        public void setTitlePrinted(boolean enabled) {
-            mTitlePrinted = enabled;
-        }
-
-        public SharedUserSetting getSharedUser() {
-            return mSharedUser;
-        }
-
-        public void setSharedUser(SharedUserSetting user) {
-            mSharedUser = user;
-        }
-    }
-
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
@@ -23401,135 +22185,6 @@
         }
     }
 
-    /*
-     * Update media status on PackageManager.
-     */
-    @Override
-    public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
-        enforceSystemOrRoot("Media status can only be updated by the system");
-        // reader; this apparently protects mMediaMounted, but should probably
-        // be a different lock in that case.
-        synchronized (mPackages) {
-            Log.i(TAG, "Updating external media status from "
-                    + (mMediaMounted ? "mounted" : "unmounted") + " to "
-                    + (mediaStatus ? "mounted" : "unmounted"));
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus
-                        + ", mMediaMounted=" + mMediaMounted);
-            if (mediaStatus == mMediaMounted) {
-                final Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1
-                        : 0, -1);
-                mHandler.sendMessage(msg);
-                return;
-            }
-            mMediaMounted = mediaStatus;
-        }
-        // Queue up an async operation since the package installation may take a
-        // little while.
-        mHandler.post(new Runnable() {
-            public void run() {
-                updateExternalMediaStatusInner(mediaStatus, reportStatus, true);
-            }
-        });
-    }
-
-    /**
-     * Called by StorageManagerService when the initial ASECs to scan are available.
-     * Should block until all the ASEC containers are finished being scanned.
-     */
-    public void scanAvailableAsecs() {
-        updateExternalMediaStatusInner(true, false, false);
-    }
-
-    /*
-     * Collect information of applications on external media, map them against
-     * existing containers and update information based on current mount status.
-     * Please note that we always have to report status if reportStatus has been
-     * set to true especially when unloading packages.
-     */
-    private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus,
-            boolean externalStorage) {
-        ArrayMap<AsecInstallArgs, String> processCids = new ArrayMap<>();
-        int[] uidArr = EmptyArray.INT;
-
-        final String[] list = PackageHelper.getSecureContainerList();
-        if (ArrayUtils.isEmpty(list)) {
-            Log.i(TAG, "No secure containers found");
-        } else {
-            // Process list of secure containers and categorize them
-            // as active or stale based on their package internal state.
-
-            // reader
-            synchronized (mPackages) {
-                for (String cid : list) {
-                    // Leave stages untouched for now; installer service owns them
-                    if (PackageInstallerService.isStageName(cid)) continue;
-
-                    if (DEBUG_SD_INSTALL)
-                        Log.i(TAG, "Processing container " + cid);
-                    String pkgName = getAsecPackageName(cid);
-                    if (pkgName == null) {
-                        Slog.i(TAG, "Found stale container " + cid + " with no package name");
-                        continue;
-                    }
-                    if (DEBUG_SD_INSTALL)
-                        Log.i(TAG, "Looking for pkg : " + pkgName);
-
-                    final PackageSetting ps = mSettings.mPackages.get(pkgName);
-                    if (ps == null) {
-                        Slog.i(TAG, "Found stale container " + cid + " with no matching settings");
-                        continue;
-                    }
-
-                    /*
-                     * Skip packages that are not external if we're unmounting
-                     * external storage.
-                     */
-                    if (externalStorage && !isMounted && !isExternal(ps)) {
-                        continue;
-                    }
-
-                    final AsecInstallArgs args = new AsecInstallArgs(cid,
-                            getAppDexInstructionSets(ps), ps.isForwardLocked());
-                    // The package status is changed only if the code path
-                    // matches between settings and the container id.
-                    if (ps.codePathString != null
-                            && ps.codePathString.startsWith(args.getCodePath())) {
-                        if (DEBUG_SD_INSTALL) {
-                            Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName
-                                    + " at code path: " + ps.codePathString);
-                        }
-
-                        // We do have a valid package installed on sdcard
-                        processCids.put(args, ps.codePathString);
-                        final int uid = ps.appId;
-                        if (uid != -1) {
-                            uidArr = ArrayUtils.appendInt(uidArr, uid);
-                        }
-                    } else {
-                        Slog.i(TAG, "Found stale container " + cid + ": expected codePath="
-                                + ps.codePathString);
-                    }
-                }
-            }
-
-            Arrays.sort(uidArr);
-        }
-
-        // Process packages with valid entries.
-        if (isMounted) {
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Loading packages");
-            loadMediaPackages(processCids, uidArr, externalStorage);
-            startCleaningPackages();
-            mInstallerService.onSecureContainersAvailable();
-        } else {
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Unloading packages");
-            unloadMediaPackages(processCids, uidArr, reportStatus);
-        }
-    }
-
     private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
             ArrayList<ApplicationInfo> infos, IIntentReceiver finishedReceiver) {
         final int size = infos.size();
@@ -23569,193 +22224,6 @@
         }
     }
 
-   /*
-     * Look at potentially valid container ids from processCids If package
-     * information doesn't match the one on record or package scanning fails,
-     * the cid is added to list of removeCids. We currently don't delete stale
-     * containers.
-     */
-    private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr,
-            boolean externalStorage) {
-        ArrayList<String> pkgList = new ArrayList<String>();
-        Set<AsecInstallArgs> keys = processCids.keySet();
-
-        for (AsecInstallArgs args : keys) {
-            String codePath = processCids.get(args);
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Loading container : " + args.cid);
-            int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-            try {
-                // Make sure there are no container errors first.
-                if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) {
-                    Slog.e(TAG, "Failed to mount cid : " + args.cid
-                            + " when installing from sdcard");
-                    continue;
-                }
-                // Check code path here.
-                if (codePath == null || !codePath.startsWith(args.getCodePath())) {
-                    Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath()
-                            + " does not match one in settings " + codePath);
-                    continue;
-                }
-                // Parse package
-                int parseFlags = mDefParseFlags;
-                if (args.isExternalAsec()) {
-                    parseFlags |= PackageParser.PARSE_EXTERNAL_STORAGE;
-                }
-                if (args.isFwdLocked()) {
-                    parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
-                }
-
-                synchronized (mInstallLock) {
-                    PackageParser.Package pkg = null;
-                    try {
-                        // Sadly we don't know the package name yet to freeze it
-                        pkg = scanPackageTracedLI(new File(codePath), parseFlags,
-                                SCAN_IGNORE_FROZEN, 0, null);
-                    } catch (PackageManagerException e) {
-                        Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
-                    }
-                    // Scan the package
-                    if (pkg != null) {
-                        /*
-                         * TODO why is the lock being held? doPostInstall is
-                         * called in other places without the lock. This needs
-                         * to be straightened out.
-                         */
-                        // writer
-                        synchronized (mPackages) {
-                            retCode = PackageManager.INSTALL_SUCCEEDED;
-                            pkgList.add(pkg.packageName);
-                            // Post process args
-                            args.doPostInstall(PackageManager.INSTALL_SUCCEEDED,
-                                    pkg.applicationInfo.uid);
-                        }
-                    } else {
-                        Slog.i(TAG, "Failed to install pkg from  " + codePath + " from sdcard");
-                    }
-                }
-
-            } finally {
-                if (retCode != PackageManager.INSTALL_SUCCEEDED) {
-                    Log.w(TAG, "Container " + args.cid + " is stale, retCode=" + retCode);
-                }
-            }
-        }
-        // writer
-        synchronized (mPackages) {
-            // If the platform SDK has changed since the last time we booted,
-            // we need to re-grant app permission to catch any new ones that
-            // appear. This is really a hack, and means that apps can in some
-            // cases get permissions that the user didn't initially explicitly
-            // allow... it would be nice to have some better way to handle
-            // this situation.
-            final VersionInfo ver = externalStorage ? mSettings.getExternalVersion()
-                    : mSettings.getInternalVersion();
-            final String volumeUuid = externalStorage ? StorageManager.UUID_PRIMARY_PHYSICAL
-                    : StorageManager.UUID_PRIVATE_INTERNAL;
-
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
-                logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
-                        + mSdkVersion + "; regranting permissions for external");
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
-            }
-            updatePermissionsLPw(null, null, volumeUuid, updateFlags);
-
-            // Yay, everything is now upgraded
-            ver.forceCurrent();
-
-            // can downgrade to reader
-            // Persist settings
-            mSettings.writeLPr();
-        }
-        // Send a broadcast to let everyone know we are done processing
-        if (pkgList.size() > 0) {
-            sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null);
-        }
-    }
-
-   /*
-     * Utility method to unload a list of specified containers
-     */
-    private void unloadAllContainers(Set<AsecInstallArgs> cidArgs) {
-        // Just unmount all valid containers.
-        for (AsecInstallArgs arg : cidArgs) {
-            synchronized (mInstallLock) {
-                arg.doPostDeleteLI(false);
-           }
-       }
-   }
-
-    /*
-     * Unload packages mounted on external media. This involves deleting package
-     * data from internal structures, sending broadcasts about disabled packages,
-     * gc'ing to free up references, unmounting all secure containers
-     * corresponding to packages on external media, and posting a
-     * UPDATED_MEDIA_STATUS message if status has been requested. Please note
-     * that we always have to post this message if status has been requested no
-     * matter what.
-     */
-    private void unloadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int uidArr[],
-            final boolean reportStatus) {
-        if (DEBUG_SD_INSTALL)
-            Log.i(TAG, "unloading media packages");
-        ArrayList<String> pkgList = new ArrayList<String>();
-        ArrayList<AsecInstallArgs> failedList = new ArrayList<AsecInstallArgs>();
-        final Set<AsecInstallArgs> keys = processCids.keySet();
-        for (AsecInstallArgs args : keys) {
-            String pkgName = args.getPackageName();
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Trying to unload pkg : " + pkgName);
-            // Delete package internally
-            PackageRemovedInfo outInfo = new PackageRemovedInfo(this);
-            synchronized (mInstallLock) {
-                final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
-                final boolean res;
-                try (PackageFreezer freezer = freezePackageForDelete(pkgName, deleteFlags,
-                        "unloadMediaPackages")) {
-                    res = deletePackageLIF(pkgName, null, false, null, deleteFlags, outInfo, false,
-                            null);
-                }
-                if (res) {
-                    pkgList.add(pkgName);
-                } else {
-                    Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName);
-                    failedList.add(args);
-                }
-            }
-        }
-
-        // reader
-        synchronized (mPackages) {
-            // We didn't update the settings after removing each package;
-            // write them now for all packages.
-            mSettings.writeLPr();
-        }
-
-        // We have to absolutely send UPDATED_MEDIA_STATUS only
-        // after confirming that all the receivers processed the ordered
-        // broadcast when packages get disabled, force a gc to clean things up.
-        // and unload all the containers.
-        if (pkgList.size() > 0) {
-            sendResourcesChangedBroadcast(false, false, pkgList, uidArr,
-                    new IIntentReceiver.Stub() {
-                public void performReceive(Intent intent, int resultCode, String data,
-                        Bundle extras, boolean ordered, boolean sticky,
-                        int sendingUser) throws RemoteException {
-                    Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS,
-                            reportStatus ? 1 : 0, 1, keys);
-                    mHandler.sendMessage(msg);
-                }
-            });
-        } else {
-            Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, -1,
-                    keys);
-            mHandler.sendMessage(msg);
-        }
-    }
-
     private void loadPrivatePackages(final VolumeInfo vol) {
         mHandler.post(new Runnable() {
             @Override
@@ -24841,7 +23309,9 @@
     }
 
     void onNewUserCreated(final int userId) {
-        mDefaultPermissionPolicy.grantDefaultPermissions(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.
@@ -24873,7 +23343,8 @@
             synchronized (mPackages) {
                 if (mSettings.mReadExternalStorageEnforced == null
                         || mSettings.mReadExternalStorageEnforced != enforced) {
-                    mSettings.mReadExternalStorageEnforced = enforced;
+                    mSettings.mReadExternalStorageEnforced =
+                            enforced ? Boolean.TRUE : Boolean.FALSE;
                     mSettings.writeLPr();
                 }
             }
@@ -25245,74 +23716,164 @@
             }
             return results;
         }
+
+        // NB: this differentiates between preloads and sideloads
+        @Override
+        public String getInstallerForPackage(String packageName) throws RemoteException {
+            final String installerName = getInstallerPackageName(packageName);
+            if (!TextUtils.isEmpty(installerName)) {
+                return installerName;
+            }
+            // differentiate between preload and sideload
+            int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+            ApplicationInfo appInfo = getApplicationInfo(packageName,
+                                    /*flags*/ 0,
+                                    /*userId*/ callingUser);
+            if (appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                return "preload";
+            }
+            return "";
+        }
+
+        @Override
+        public int getVersionCodeForPackage(String packageName) throws RemoteException {
+            try {
+                int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+                PackageInfo pInfo = getPackageInfo(packageName, 0, callingUser);
+                if (pInfo != null) {
+                    return pInfo.versionCode;
+                }
+            } catch (Exception e) {
+            }
+            return 0;
+        }
     }
 
     private class PackageManagerInternalImpl extends PackageManagerInternal {
         @Override
-        public void setLocationPackagesProvider(PackagesProvider provider) {
+        public void updatePermissionFlagsTEMP(String permName, String packageName, int flagMask,
+                int flagValues, int userId) {
+            PackageManagerService.this.updatePermissionFlags(
+                    permName, packageName, flagMask, flagValues, userId);
+        }
+
+        @Override
+        public int getPermissionFlagsTEMP(String permName, String packageName, int userId) {
+            return PackageManagerService.this.getPermissionFlags(permName, packageName, userId);
+        }
+
+        @Override
+        public Object enforcePermissionTreeTEMP(String permName, int callingUid) {
             synchronized (mPackages) {
-                mDefaultPermissionPolicy.setLocationPackagesProviderLPw(provider);
+                return BasePermission.enforcePermissionTreeLP(
+                        mSettings.mPermissionTrees, permName, callingUid);
             }
         }
+        @Override
+        public boolean isInstantApp(String packageName, int userId) {
+            return PackageManagerService.this.isInstantApp(packageName, userId);
+        }
+
+        @Override
+        public String getInstantAppPackageName(int uid) {
+            return PackageManagerService.this.getInstantAppPackageName(uid);
+        }
+
+        @Override
+        public boolean filterAppAccess(PackageParser.Package pkg, int callingUid, int userId) {
+            synchronized (mPackages) {
+                return PackageManagerService.this.filterAppAccessLPr(
+                        (PackageSetting) pkg.mExtras, callingUid, userId);
+            }
+        }
+
+        @Override
+        public PackageParser.Package getPackage(String packageName) {
+            synchronized (mPackages) {
+                packageName = resolveInternalPackageNameLPr(
+                        packageName, PackageManager.VERSION_CODE_HIGHEST);
+                return mPackages.get(packageName);
+            }
+        }
+
+        @Override
+        public PackageParser.Package getDisabledPackage(String packageName) {
+            synchronized (mPackages) {
+                final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
+                return (ps != null) ? ps.pkg : null;
+            }
+        }
+
+        @Override
+        public String getKnownPackageName(int knownPackage, int userId) {
+            switch(knownPackage) {
+                case PackageManagerInternal.PACKAGE_BROWSER:
+                    return getDefaultBrowserPackageName(userId);
+                case PackageManagerInternal.PACKAGE_INSTALLER:
+                    return mRequiredInstallerPackage;
+                case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
+                    return mSetupWizardPackage;
+                case PackageManagerInternal.PACKAGE_SYSTEM:
+                    return "android";
+                case PackageManagerInternal.PACKAGE_VERIFIER:
+                    return mRequiredVerifierPackage;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isResolveActivityComponent(ComponentInfo component) {
+            return mResolveActivity.packageName.equals(component.packageName)
+                    && mResolveActivity.name.equals(component.name);
+        }
+
+        @Override
+        public void setLocationPackagesProvider(PackagesProvider provider) {
+            mDefaultPermissionPolicy.setLocationPackagesProvider(provider);
+        }
 
         @Override
         public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setVoiceInteractionPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setVoiceInteractionPackagesProvider(provider);
         }
 
         @Override
         public void setSmsAppPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSmsAppPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSmsAppPackagesProvider(provider);
         }
 
         @Override
         public void setDialerAppPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setDialerAppPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setDialerAppPackagesProvider(provider);
         }
 
         @Override
         public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSimCallManagerPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSimCallManagerPackagesProvider(provider);
         }
 
         @Override
         public void setSyncAdapterPackagesprovider(SyncAdapterPackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSyncAdapterPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSyncAdapterPackagesProvider(provider);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsAppLPr(
-                        packageName, userId);
-            }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsApp(packageName, userId);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
             synchronized (mPackages) {
                 mSettings.setDefaultDialerPackageNameLPw(packageName, userId);
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerAppLPr(
-                        packageName, userId);
             }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerApp(packageName, userId);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManagerLPr(
-                        packageName, userId);
-            }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManager(
+                    packageName, userId);
         }
 
         @Override
@@ -25399,6 +23960,15 @@
         }
 
         @Override
+        public List<ResolveInfo> queryIntentServices(
+                Intent intent, int flags, int callingUid, int userId) {
+            final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
+            return PackageManagerService.this
+                    .queryIntentServicesInternal(intent, resolvedType, flags, userId, callingUid,
+                            false);
+        }
+
+        @Override
         public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
                 int userId) {
             return PackageManagerService.this.getHomeActivitiesAsUser(allHomeCandidates, userId);
@@ -25433,17 +24003,19 @@
         }
 
         @Override
-        public void grantRuntimePermission(String packageName, String name, int userId,
+        public void grantRuntimePermission(String packageName, String permName, int userId,
                 boolean overridePolicy) {
-            PackageManagerService.this.grantRuntimePermission(packageName, name, userId,
-                    overridePolicy);
+            PackageManagerService.this.mPermissionManager.grantRuntimePermission(
+                    permName, packageName, overridePolicy, getCallingUid(), userId,
+                    mPermissionCallback);
         }
 
         @Override
-        public void revokeRuntimePermission(String packageName, String name, int userId,
+        public void revokeRuntimePermission(String packageName, String permName, int userId,
                 boolean overridePolicy) {
-            PackageManagerService.this.revokeRuntimePermission(packageName, name, userId,
-                    overridePolicy);
+            mPermissionManager.revokeRuntimePermission(
+                    permName, packageName, overridePolicy, getCallingUid(), userId,
+                    mPermissionCallback);
         }
 
         @Override
@@ -25565,9 +24137,9 @@
 
         @Override
         public ResolveInfo resolveIntent(Intent intent, String resolvedType,
-                int flags, int userId) {
+                int flags, int userId, boolean resolveForStart) {
             return resolveIntentInternal(
-                    intent, resolvedType, flags, userId, true /*resolveForStart*/);
+                    intent, resolvedType, flags, userId, resolveForStart);
         }
 
         @Override
@@ -25577,6 +24149,12 @@
         }
 
         @Override
+        public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+            return PackageManagerService.this.resolveContentProviderInternal(
+                    name, flags, userId);
+        }
+
+        @Override
         public void addIsolatedUid(int isolatedUid, int ownerUid) {
             synchronized (mPackages) {
                 mIsolatedOwners.put(isolatedUid, ownerUid);
@@ -25623,7 +24201,7 @@
         synchronized (mPackages) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierAppsLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierApps(
                         packageNames, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -25637,7 +24215,7 @@
         synchronized (mPackages) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServicesLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServices(
                         packageNames, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -25712,7 +24290,7 @@
     @Override
     public int getInstallReason(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get install reason");
         synchronized (mPackages) {
@@ -25790,7 +24368,7 @@
     public String getInstantAppAndroidId(String packageName, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS,
                 "getInstantAppAndroidId");
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppAndroidId");
         // Make sure the target is an Instant App.
diff --git a/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 1a97a72..19b0d9b 100644
--- a/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -26,14 +26,19 @@
 public class PackageManagerServiceCompilerMapping {
     // Names for compilation reasons.
     static final String REASON_STRINGS[] = {
-            "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive"
+            "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive", "shared"
     };
 
+    static final int REASON_SHARED_INDEX = 6;
+
     // Static block to ensure the strings array is of the right length.
     static {
         if (PackageManagerService.REASON_LAST + 1 != REASON_STRINGS.length) {
             throw new IllegalStateException("REASON_STRINGS not correct");
         }
+        if (!"shared".equals(REASON_STRINGS[REASON_SHARED_INDEX])) {
+            throw new IllegalStateException("REASON_STRINGS not correct because of shared index");
+        }
     }
 
     private static String getSystemPropertyName(int reason) {
@@ -52,11 +57,18 @@
                 !DexFile.isValidCompilerFilter(sysPropValue)) {
             throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
                     + "(reason " + REASON_STRINGS[reason] + ")");
+        } else if (!isFilterAllowedForReason(reason, sysPropValue)) {
+            throw new IllegalStateException("Value \"" + sysPropValue +"\" not allowed "
+                    + "(reason " + REASON_STRINGS[reason] + ")");
         }
 
         return sysPropValue;
     }
 
+    private static boolean isFilterAllowedForReason(int reason, String filter) {
+        return reason != REASON_SHARED_INDEX || !DexFile.isProfileGuidedCompilerFilter(filter);
+    }
+
     // Check that the properties are set and valid.
     // Note: this is done in a separate method so this class can be statically initialized.
     static void checkProperties() {
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 25fef0a..8f7971e 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -22,17 +22,23 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 import static com.android.server.pm.PackageManagerService.TAG;
 
+import com.android.internal.util.ArrayUtils;
+
 import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.content.Intent;
 import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
+import android.os.Debug;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.system.ErrnoException;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
+import android.util.jar.StrictJarFile;
 import dalvik.system.VMRuntime;
 import libcore.io.Libcore;
 
@@ -41,9 +47,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
 
 /**
  * Class containing helper methods for the PackageManagerService.
@@ -253,4 +261,73 @@
         }
         return false;
     }
+
+    /**
+     * Checks that the archive located at {@code fileName} has uncompressed dex file and so
+     * files that can be direclty mapped.
+     */
+    public static void logApkHasUncompressedCode(String fileName) {
+        StrictJarFile jarFile = null;
+        try {
+            jarFile = new StrictJarFile(fileName,
+                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+            Iterator<ZipEntry> it = jarFile.iterator();
+            while (it.hasNext()) {
+                ZipEntry entry = it.next();
+                if (entry.getName().endsWith(".dex")) {
+                    if (entry.getMethod() != ZipEntry.STORED) {
+                        Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
+                                entry.getName());
+                    } else if ((entry.getDataOffset() & 0x3) != 0) {
+                        Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
+                                entry.getName());
+                    }
+                } else if (entry.getName().endsWith(".so")) {
+                    if (entry.getMethod() != ZipEntry.STORED) {
+                        Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
+                                entry.getName());
+                    } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
+                        Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
+                                entry.getName());
+                    }
+                }
+            }
+        } catch (IOException ignore) {
+            Slog.wtf(TAG, "Error when parsing APK " + fileName);
+        } finally {
+            try {
+                if (jarFile != null) {
+                    jarFile.close();
+                }
+            } catch (IOException ignore) {}
+        }
+        return;
+    }
+
+    /**
+     * Checks that the APKs in the given package have uncompressed dex file and so
+     * files that can be direclty mapped.
+     */
+    public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
+        logApkHasUncompressedCode(pkg.baseCodePath);
+        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+                logApkHasUncompressedCode(pkg.splitCodePaths[i]);
+            }
+        }
+    }
+
+    public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
+        if (callingUid == Process.SHELL_UID) {
+            if (userHandle >= 0
+                    && PackageManagerService.sUserManager.hasUserRestriction(
+                            restriction, userHandle)) {
+                throw new SecurityException("Shell does not have permission to access user "
+                        + userHandle);
+            } else if (userHandle < 0) {
+                Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
+                        + userHandle + "\n\t" + Debug.getCallers(3));
+            }
+        }
+    }
 }
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index 930e4f0..1fea003 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -183,7 +183,7 @@
                     PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                             null, null);
                     params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
-                            pkgLite, false, params.sessionParams.abiOverride));
+                            pkgLite, params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
                     pw.println("Error: Failed to parse APK file: " + file);
                     throw new IllegalArgumentException(
@@ -1441,7 +1441,7 @@
             out = session.openWrite(splitName, 0, sizeBytes);
 
             int total = 0;
-            byte[] buffer = new byte[65536];
+            byte[] buffer = new byte[1024 * 1024];
             int c;
             while ((c = in.read(buffer)) != -1) {
                 total += c;
diff --git a/com/android/server/pm/PackageSetting.java b/com/android/server/pm/PackageSetting.java
index 52bf641..83cb2db 100644
--- a/com/android/server/pm/PackageSetting.java
+++ b/com/android/server/pm/PackageSetting.java
@@ -23,13 +23,15 @@
 import android.service.pm.PackageProto;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.pm.permission.PermissionsState;
+
 import java.io.File;
 import java.util.List;
 
 /**
  * Settings data for a particular package we know about.
  */
-final class PackageSetting extends PackageSettingBase {
+public final class PackageSetting extends PackageSettingBase {
     int appId;
     PackageParser.Package pkg;
     /**
@@ -103,12 +105,21 @@
         sharedUserId = orig.sharedUserId;
     }
 
+    @Override
     public PermissionsState getPermissionsState() {
         return (sharedUser != null)
                 ? sharedUser.getPermissionsState()
                 : super.getPermissionsState();
     }
 
+    public PackageParser.Package getPackage() {
+        return pkg;
+    }
+
+    public int getAppId() {
+        return appId;
+    }
+
     public boolean isPrivileged() {
         return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
@@ -125,6 +136,7 @@
         return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
 
+    @Override
     public boolean isSharedUser() {
         return sharedUser != null;
     }
@@ -136,6 +148,10 @@
         return true;
     }
 
+    public boolean hasChildPackages() {
+        return childPackageNames != null && !childPackageNames.isEmpty();
+    }
+
     public void writeToProto(ProtoOutputStream proto, long fieldId, List<UserInfo> users) {
         final long packageToken = proto.start(fieldId);
         proto.write(PackageProto.NAME, (realName != null ? realName : name));
diff --git a/com/android/server/pm/PackageSettingBase.java b/com/android/server/pm/PackageSettingBase.java
index d3ca1fd..e19e83f 100644
--- a/com/android/server/pm/PackageSettingBase.java
+++ b/com/android/server/pm/PackageSettingBase.java
@@ -24,14 +24,12 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
-import android.os.storage.VolumeInfo;
 import android.service.pm.PackageProto;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.google.android.collect.Lists;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -42,7 +40,7 @@
 /**
  * Settings base class for pending and resolved classes.
  */
-abstract class PackageSettingBase extends SettingBase {
+public abstract class PackageSettingBase extends SettingBase {
 
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
@@ -230,6 +228,9 @@
         return updateAvailable;
     }
 
+    public boolean isSharedUser() {
+        return false;
+    }
     /**
      * Makes a shallow copy of the given package settings.
      *
@@ -412,7 +413,7 @@
         modifyUserState(userId).suspended = suspended;
     }
 
-    boolean getInstantApp(int userId) {
+    public boolean getInstantApp(int userId) {
         return readUserState(userId).instantApp;
     }
 
diff --git a/com/android/server/pm/SettingBase.java b/com/android/server/pm/SettingBase.java
index e17cec0..c97f5e5 100644
--- a/com/android/server/pm/SettingBase.java
+++ b/com/android/server/pm/SettingBase.java
@@ -18,6 +18,8 @@
 
 import android.content.pm.ApplicationInfo;
 
+import com.android.server.pm.permission.PermissionsState;
+
 abstract class SettingBase {
     int pkgFlags;
     int pkgPrivateFlags;
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 51d3e10..0084411 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -64,7 +63,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
 import android.service.pm.PackageServiceDumpProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -87,10 +85,11 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
-import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PackageManagerService.DumpState;
-import com.android.server.pm.PermissionsState.PermissionState;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.PermissionSettings;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
 
 import libcore.io.IoUtils;
 
@@ -127,7 +126,7 @@
 /**
  * Holds information about dynamic settings.
  */
-final class Settings {
+public final class Settings {
     private static final String TAG = "PackageSettings";
 
     /**
@@ -176,7 +175,7 @@
     private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
     private static final String ATTR_ENFORCEMENT = "enforcement";
 
-    private static final String TAG_ITEM = "item";
+    public static final String TAG_ITEM = "item";
     private static final String TAG_DISABLED_COMPONENTS = "disabled-components";
     private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
     private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
@@ -201,7 +200,8 @@
     private static final String TAG_DEFAULT_DIALER = "default-dialer";
     private static final String TAG_VERSION = "version";
 
-    private static final String ATTR_NAME = "name";
+    public static final String ATTR_NAME = "name";
+    public static final String ATTR_PACKAGE = "package";
     private static final String ATTR_USER = "user";
     private static final String ATTR_CODE = "code";
     private static final String ATTR_GRANTED = "granted";
@@ -233,7 +233,6 @@
     private static final String ATTR_VOLUME_UUID = "volumeUuid";
     private static final String ATTR_SDK_VERSION = "sdkVersion";
     private static final String ATTR_DATABASE_VERSION = "databaseVersion";
-    private static final String ATTR_DONE = "done";
 
     // Bookkeeping for restored permission grants
     private static final String TAG_RESTORED_RUNTIME_PERMISSIONS = "restored-perms";
@@ -379,10 +378,6 @@
     private final ArrayMap<Long, Integer> mKeySetRefs =
             new ArrayMap<Long, Integer>();
 
-    // Mapping from permission names to info about them.
-    final ArrayMap<String, BasePermission> mPermissions =
-            new ArrayMap<String, BasePermission>();
-
     // Mapping from permission tree names to info about them.
     final ArrayMap<String, BasePermission> mPermissionTrees =
             new ArrayMap<String, BasePermission>();
@@ -420,14 +415,16 @@
     private final File mSystemDir;
 
     public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
+    /** Settings and other information about permissions */
+    private final PermissionSettings mPermissions;
 
-    Settings(Object lock) {
-        this(Environment.getDataDirectory(), lock);
+    Settings(PermissionSettings permissions, Object lock) {
+        this(Environment.getDataDirectory(), permissions, lock);
     }
 
-    Settings(File dataDir, Object lock) {
+    Settings(File dataDir, PermissionSettings permission, Object lock) {
         mLock = lock;
-
+        mPermissions = permission;
         mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
 
         mSystemDir = new File(dataDir, "system");
@@ -490,7 +487,7 @@
         final PermissionsState perms = ps.getPermissionsState();
 
         for (RestoredPermissionGrant grant : grants) {
-            BasePermission bp = mPermissions.get(grant.permissionName);
+            BasePermission bp = mPermissions.getPermission(grant.permissionName);
             if (bp != null) {
                 if (grant.granted) {
                     perms.grantRuntimePermission(bp, userId);
@@ -507,6 +504,10 @@
         writeRuntimePermissionsForUserLPr(userId, false);
     }
 
+    public boolean canPropagatePermissionToInstantApp(String permName) {
+        return mPermissions.canPropagatePermissionToInstantApp(permName);
+    }
+
     void setInstallerPackageName(String pkgName, String installerPkgName) {
         PackageSetting p = mPackages.get(pkgName);
         if (p != null) {
@@ -664,29 +665,11 @@
         }
     }
 
-    // Transfer ownership of permissions from one package to another.
-    void transferPermissionsLPw(String origPkg, String newPkg) {
-        // Transfer ownership of permissions to the new package.
-        for (int i=0; i<2; i++) {
-            ArrayMap<String, BasePermission> permissions =
-                    i == 0 ? mPermissionTrees : mPermissions;
-            for (BasePermission bp : permissions.values()) {
-                if (origPkg.equals(bp.sourcePackage)) {
-                    if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG,
-                            "Moving permission " + bp.name
-                            + " from pkg " + bp.sourcePackage
-                            + " to " + newPkg);
-                    bp.sourcePackage = newPkg;
-                    bp.packageSetting = null;
-                    bp.perm = null;
-                    if (bp.pendingInfo != null) {
-                        bp.pendingInfo.packageName = newPkg;
-                    }
-                    bp.uid = 0;
-                    bp.setGids(null, false);
-                }
-            }
-        }
+    /**
+     * Transfers ownership of permissions from one package to another.
+     */
+    void transferPermissionsLPw(String origPackageName, String newPackageName) {
+        mPermissions.transferPermissions(origPackageName, newPackageName, mPermissionTrees);
     }
 
     /**
@@ -1074,7 +1057,7 @@
 
         // Update permissions
         for (String eachPerm : deletedPs.pkg.requestedPermissions) {
-            BasePermission bp = mPermissions.get(eachPerm);
+            BasePermission bp = mPermissions.getPermission(eachPerm);
             if (bp == null) {
                 continue;
             }
@@ -2022,8 +2005,7 @@
 
     // Specifically for backup/restore
     public void processRestoredPermissionGrantLPr(String pkgName, String permission,
-            boolean isGranted, int restoredFlagSet, int userId)
-            throws IOException, XmlPullParserException {
+            boolean isGranted, int restoredFlagSet, int userId) {
         mRuntimePermissionsPersistence.rememberRestoredUserGrantLPr(
                 pkgName, permission, isGranted, restoredFlagSet, userId);
     }
@@ -2225,7 +2207,7 @@
             if (tagName.equals(TAG_ITEM)) {
                 String name = parser.getAttributeValue(null, ATTR_NAME);
 
-                BasePermission bp = mPermissions.get(name);
+                BasePermission bp = mPermissions.getPermission(name);
                 if (bp == null) {
                     Slog.w(PackageManagerService.TAG, "Unknown permission: " + name);
                     XmlUtils.skipCurrentTag(parser);
@@ -2520,9 +2502,7 @@
             serializer.endTag(null, "permission-trees");
 
             serializer.startTag(null, "permissions");
-            for (BasePermission bp : mPermissions.values()) {
-                writePermissionLPr(serializer, bp);
-            }
+            mPermissions.writePermissions(serializer);
             serializer.endTag(null, "permissions");
 
             for (final PackageSetting pkg : mPackages.values()) {
@@ -2605,9 +2585,6 @@
             writeAllRuntimePermissionsLPr();
             return;
 
-        } catch(XmlPullParserException e) {
-            Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
-                    + "current changes will be lost at reboot", e);
         } catch(java.io.IOException e) {
             Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
                     + "current changes will be lost at reboot", e);
@@ -2951,7 +2928,6 @@
 
     void writeUpgradeKeySetsLPr(XmlSerializer serializer,
             PackageKeySetData data) throws IOException {
-        long properSigning = data.getProperSigningKeySet();
         if (data.isUsingUpgradeKeySets()) {
             for (long id : data.getUpgradeKeySets()) {
                 serializer.startTag(null, "upgrade-keyset");
@@ -2971,32 +2947,8 @@
         }
     }
 
-    void writePermissionLPr(XmlSerializer serializer, BasePermission bp)
-            throws XmlPullParserException, java.io.IOException {
-        if (bp.sourcePackage != null) {
-            serializer.startTag(null, TAG_ITEM);
-            serializer.attribute(null, ATTR_NAME, bp.name);
-            serializer.attribute(null, "package", bp.sourcePackage);
-            if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
-                serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel));
-            }
-            if (PackageManagerService.DEBUG_SETTINGS)
-                Log.v(PackageManagerService.TAG, "Writing perm: name=" + bp.name + " type="
-                        + bp.type);
-            if (bp.type == BasePermission.TYPE_DYNAMIC) {
-                final PermissionInfo pi = bp.perm != null ? bp.perm.info : bp.pendingInfo;
-                if (pi != null) {
-                    serializer.attribute(null, "type", "dynamic");
-                    if (pi.icon != 0) {
-                        serializer.attribute(null, "icon", Integer.toString(pi.icon));
-                    }
-                    if (pi.nonLocalizedLabel != null) {
-                        serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
-                    }
-                }
-            }
-            serializer.endTag(null, TAG_ITEM);
-        }
+    void writePermissionLPr(XmlSerializer serializer, BasePermission bp) throws IOException {
+        bp.writeLPr(serializer);
     }
 
     ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() {
@@ -3088,9 +3040,9 @@
                 if (tagName.equals("package")) {
                     readPackageLPw(parser);
                 } else if (tagName.equals("permissions")) {
-                    readPermissionsLPw(mPermissions, parser);
+                    mPermissions.readPermissions(parser);
                 } else if (tagName.equals("permission-trees")) {
-                    readPermissionsLPw(mPermissionTrees, parser);
+                    PermissionSettings.readPermissions(mPermissionTrees, parser);
                 } else if (tagName.equals("shared-user")) {
                     readSharedUserLPw(parser);
                 } else if (tagName.equals("preferred-packages")) {
@@ -3169,7 +3121,8 @@
                     }
                 } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                     final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
-                    mReadExternalStorageEnforced = "1".equals(enforcement);
+                    mReadExternalStorageEnforced =
+                            "1".equals(enforcement) ? Boolean.TRUE : Boolean.FALSE;
                 } else if (tagName.equals("keyset-settings")) {
                     mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs);
                 } else if (TAG_VERSION.equals(tagName)) {
@@ -3593,72 +3546,6 @@
         }
     }
 
-    private int readInt(XmlPullParser parser, String ns, String name, int defValue) {
-        String v = parser.getAttributeValue(ns, name);
-        try {
-            if (v == null) {
-                return defValue;
-            }
-            return Integer.parseInt(v);
-        } catch (NumberFormatException e) {
-            PackageManagerService.reportSettingsProblem(Log.WARN,
-                    "Error in package manager settings: attribute " + name
-                            + " has bad integer value " + v + " at "
-                            + parser.getPositionDescription());
-        }
-        return defValue;
-    }
-
-    private void readPermissionsLPw(ArrayMap<String, BasePermission> out, XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            final String tagName = parser.getName();
-            if (tagName.equals(TAG_ITEM)) {
-                final String name = parser.getAttributeValue(null, ATTR_NAME);
-                final String sourcePackage = parser.getAttributeValue(null, "package");
-                final String ptype = parser.getAttributeValue(null, "type");
-                if (name != null && sourcePackage != null) {
-                    final boolean dynamic = "dynamic".equals(ptype);
-                    BasePermission bp = out.get(name);
-                    // If the permission is builtin, do not clobber it.
-                    if (bp == null || bp.type != BasePermission.TYPE_BUILTIN) {
-                        bp = new BasePermission(name.intern(), sourcePackage,
-                                dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
-                    }
-                    bp.protectionLevel = readInt(parser, null, "protection",
-                            PermissionInfo.PROTECTION_NORMAL);
-                    bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
-                    if (dynamic) {
-                        PermissionInfo pi = new PermissionInfo();
-                        pi.packageName = sourcePackage.intern();
-                        pi.name = name.intern();
-                        pi.icon = readInt(parser, null, "icon", 0);
-                        pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
-                        pi.protectionLevel = bp.protectionLevel;
-                        bp.pendingInfo = pi;
-                    }
-                    out.put(bp.name, bp);
-                } else {
-                    PackageManagerService.reportSettingsProblem(Log.WARN,
-                            "Error in package manager settings: permissions has" + " no name at "
-                                    + parser.getPositionDescription());
-                }
-            } else {
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "Unknown element reading permissions: " + parser.getName() + " at "
-                                + parser.getPositionDescription());
-            }
-            XmlUtils.skipCurrentTag(parser);
-        }
-    }
-
     private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException,
             IOException {
         String name = parser.getAttributeValue(null, ATTR_NAME);
@@ -4385,10 +4272,6 @@
         return ps;
     }
 
-    private String compToString(ArraySet<String> cmp) {
-        return cmp != null ? Arrays.toString(cmp.toArray()) : "[]";
-    }
-
     boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, int flags, int userId) {
         final PackageSetting ps = mPackages.get(componentInfo.packageName);
         if (ps == null) return false;
@@ -5001,45 +4884,8 @@
 
     void dumpPermissionsLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
             DumpState dumpState) {
-        boolean printedSomething = false;
-        for (BasePermission p : mPermissions.values()) {
-            if (packageName != null && !packageName.equals(p.sourcePackage)) {
-                continue;
-            }
-            if (permissionNames != null && !permissionNames.contains(p.name)) {
-                continue;
-            }
-            if (!printedSomething) {
-                if (dumpState.onTitlePrinted())
-                    pw.println();
-                pw.println("Permissions:");
-                printedSomething = true;
-            }
-            pw.print("  Permission ["); pw.print(p.name); pw.print("] (");
-                    pw.print(Integer.toHexString(System.identityHashCode(p)));
-                    pw.println("):");
-            pw.print("    sourcePackage="); pw.println(p.sourcePackage);
-            pw.print("    uid="); pw.print(p.uid);
-                    pw.print(" gids="); pw.print(Arrays.toString(
-                            p.computeGids(UserHandle.USER_SYSTEM)));
-                    pw.print(" type="); pw.print(p.type);
-                    pw.print(" prot=");
-                    pw.println(PermissionInfo.protectionToString(p.protectionLevel));
-            if (p.perm != null) {
-                pw.print("    perm="); pw.println(p.perm);
-                if ((p.perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
-                        || (p.perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
-                    pw.print("    flags=0x"); pw.println(Integer.toHexString(p.perm.info.flags));
-                }
-            }
-            if (p.packageSetting != null) {
-                pw.print("    packageSetting="); pw.println(p.packageSetting);
-            }
-            if (READ_EXTERNAL_STORAGE.equals(p.name)) {
-                pw.print("    enforced=");
-                pw.println(mReadExternalStorageEnforced);
-            }
-        }
+        mPermissions.dumpPermissions(pw, packageName, permissionNames,
+                (mReadExternalStorageEnforced == Boolean.TRUE), dumpState);
     }
 
     void dumpSharedUsersLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
@@ -5248,7 +5094,7 @@
 
         private final Handler mHandler = new MyHandler();
 
-        private final Object mLock;
+        private final Object mPersistenceLock;
 
         @GuardedBy("mLock")
         private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
@@ -5265,8 +5111,8 @@
         // The mapping keys are user ids.
         private final SparseBooleanArray mDefaultPermissionsGranted = new SparseBooleanArray();
 
-        public RuntimePermissionPersistence(Object lock) {
-            mLock = lock;
+        public RuntimePermissionPersistence(Object persistenceLock) {
+            mPersistenceLock = persistenceLock;
         }
 
         public boolean areDefaultRuntimPermissionsGrantedLPr(int userId) {
@@ -5321,7 +5167,7 @@
             ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
             ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
 
-            synchronized (mLock) {
+            synchronized (mPersistenceLock) {
                 mWriteScheduled.delete(userId);
 
                 final int packageCount = mPackages.size();
@@ -5470,7 +5316,7 @@
             PermissionsState permissionsState = sb.getPermissionsState();
             for (PermissionState permissionState
                     : permissionsState.getRuntimePermissionStates(userId)) {
-                BasePermission bp = mPermissions.get(permissionState.getName());
+                BasePermission bp = mPermissions.getPermission(permissionState.getName());
                 if (bp != null) {
                     permissionsState.revokeRuntimePermission(bp, userId);
                     permissionsState.updatePermissionFlags(bp, userId,
@@ -5631,7 +5477,7 @@
                 switch (parser.getName()) {
                     case TAG_ITEM: {
                         String name = parser.getAttributeValue(null, ATTR_NAME);
-                        BasePermission bp = mPermissions.get(name);
+                        BasePermission bp = mPermissions.getPermission(name);
                         if (bp == null) {
                             Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
                             XmlUtils.skipCurrentTag(parser);
diff --git a/com/android/server/pm/SharedUserSetting.java b/com/android/server/pm/SharedUserSetting.java
index 06e020a..a0dadae 100644
--- a/com/android/server/pm/SharedUserSetting.java
+++ b/com/android/server/pm/SharedUserSetting.java
@@ -16,12 +16,18 @@
 
 package com.android.server.pm;
 
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
 import android.util.ArraySet;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 /**
  * Settings data for a particular shared user ID we know about.
  */
-final class SharedUserSetting extends SettingBase {
+public final class SharedUserSetting extends SettingBase {
     final String name;
 
     int userId;
@@ -73,4 +79,18 @@
             setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags);
         }
     }
+
+    public @Nullable List<PackageParser.Package> getPackages() {
+        if (packages == null || packages.size() == 0) {
+            return null;
+        }
+        final ArrayList<PackageParser.Package> pkgList = new ArrayList<>(packages.size());
+        for (PackageSetting ps : packages) {
+            if (ps == null) {
+                continue;
+            }
+            pkgList.add(ps.pkg);
+        }
+        return pkgList;
+    }
 }
diff --git a/com/android/server/pm/ShortcutLauncher.java b/com/android/server/pm/ShortcutLauncher.java
index 3060840..f922ad1 100644
--- a/com/android/server/pm/ShortcutLauncher.java
+++ b/com/android/server/pm/ShortcutLauncher.java
@@ -25,6 +25,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import org.json.JSONException;
@@ -293,7 +294,7 @@
         return ret;
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
         pw.print(prefix);
diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java
index 6f70f4c..6fc1e73 100644
--- a/com/android/server/pm/ShortcutPackage.java
+++ b/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutService.ShortcutOperation;
 import com.android.server.pm.ShortcutService.Stats;
 
@@ -1144,7 +1145,7 @@
         return false;
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
         pw.print(prefix);
@@ -1186,9 +1187,7 @@
         final int size = shortcuts.size();
         for (int i = 0; i < size; i++) {
             final ShortcutInfo si = shortcuts.valueAt(i);
-            pw.print(prefix);
-            pw.print("    ");
-            pw.println(si.toInsecureString());
+            pw.println(si.toDumpString(prefix + "    "));
             if (si.getBitmapPath() != null) {
                 final long len = new File(si.getBitmapPath()).length();
                 pw.print(prefix);
diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java
index 0e572d8..27560c5 100644
--- a/com/android/server/pm/ShortcutService.java
+++ b/com/android/server/pm/ShortcutService.java
@@ -40,8 +40,8 @@
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 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;
@@ -134,6 +134,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 /**
  * TODO:
@@ -484,12 +485,13 @@
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
-            handleOnUidStateChanged(uid, procState);
+            injectPostToHandler(() -> handleOnUidStateChanged(uid, procState));
         }
 
         @Override
         public void onUidGone(int uid, boolean disabled) {
-            handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
+            injectPostToHandler(() ->
+                    handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT));
         }
 
         @Override
@@ -3454,121 +3456,265 @@
 
     @VisibleForTesting
     void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final DumpFilter filter = parseDumpArgs(args);
 
-        boolean dumpMain = true;
-        boolean checkin = false;
-        boolean clear = false;
-        boolean dumpUid = false;
-        boolean dumpFiles = false;
-
-        if (args != null) {
-            for (String arg : args) {
-                if ("-c".equals(arg)) {
-                    checkin = true;
-
-                } else if ("--checkin".equals(arg)) {
-                    checkin = true;
-                    clear = true;
-
-                } else if ("-a".equals(arg) || "--all".equals(arg)) {
-                    dumpUid = true;
-                    dumpFiles = true;
-
-                } else if ("-u".equals(arg) || "--uid".equals(arg)) {
-                    dumpUid = true;
-
-                } else if ("-f".equals(arg) || "--files".equals(arg)) {
-                    dumpFiles = true;
-
-                } else if ("-n".equals(arg) || "--no-main".equals(arg)) {
-                    dumpMain = false;
-                }
-            }
-        }
-
-        if (checkin) {
+        if (filter.shouldDumpCheckIn()) {
             // Other flags are not supported for checkin.
-            dumpCheckin(pw, clear);
+            dumpCheckin(pw, filter.shouldCheckInClear());
         } else {
-            if (dumpMain) {
-                dumpInner(pw);
+            if (filter.shouldDumpMain()) {
+                dumpInner(pw, filter);
                 pw.println();
             }
-            if (dumpUid) {
+            if (filter.shouldDumpUid()) {
                 dumpUid(pw);
                 pw.println();
             }
-            if (dumpFiles) {
+            if (filter.shouldDumpFiles()) {
                 dumpDumpFiles(pw);
                 pw.println();
             }
         }
     }
 
-    private void dumpInner(PrintWriter pw) {
-        synchronized (mLock) {
-            final long now = injectCurrentTimeMillis();
-            pw.print("Now: [");
-            pw.print(now);
-            pw.print("] ");
-            pw.print(formatTime(now));
+    private static DumpFilter parseDumpArgs(String[] args) {
+        final DumpFilter filter = new DumpFilter();
+        if (args == null) {
+            return filter;
+        }
 
-            pw.print("  Raw last reset: [");
-            pw.print(mRawLastResetTime);
-            pw.print("] ");
-            pw.print(formatTime(mRawLastResetTime));
+        int argIndex = 0;
+        while (argIndex < args.length) {
+            final String arg = args[argIndex++];
 
-            final long last = getLastResetTimeLocked();
-            pw.print("  Last reset: [");
-            pw.print(last);
-            pw.print("] ");
-            pw.print(formatTime(last));
+            if ("-c".equals(arg)) {
+                filter.setDumpCheckIn(true);
+                continue;
+            }
+            if ("--checkin".equals(arg)) {
+                filter.setDumpCheckIn(true);
+                filter.setCheckInClear(true);
+                continue;
+            }
+            if ("-a".equals(arg) || "--all".equals(arg)) {
+                filter.setDumpUid(true);
+                filter.setDumpFiles(true);
+                continue;
+            }
+            if ("-u".equals(arg) || "--uid".equals(arg)) {
+                filter.setDumpUid(true);
+                continue;
+            }
+            if ("-f".equals(arg) || "--files".equals(arg)) {
+                filter.setDumpFiles(true);
+                continue;
+            }
+            if ("-n".equals(arg) || "--no-main".equals(arg)) {
+                filter.setDumpMain(false);
+                continue;
+            }
+            if ("--user".equals(arg)) {
+                if (argIndex >= args.length) {
+                    throw new IllegalArgumentException("Missing user ID for --user");
+                }
+                try {
+                    filter.addUser(Integer.parseInt(args[argIndex++]));
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("Invalid user ID", e);
+                }
+                continue;
+            }
+            if ("-p".equals(arg) || "--package".equals(arg)) {
+                if (argIndex >= args.length) {
+                    throw new IllegalArgumentException("Missing package name for --package");
+                }
+                filter.addPackageRegex(args[argIndex++]);
+                filter.setDumpDetails(false);
+                continue;
+            }
+            if (arg.startsWith("-")) {
+                throw new IllegalArgumentException("Unknown option " + arg);
+            }
+            break;
+        }
+        while (argIndex < args.length) {
+            filter.addPackage(args[argIndex++]);
+        }
+        return filter;
+    }
 
-            final long next = getNextResetTimeLocked();
-            pw.print("  Next reset: [");
-            pw.print(next);
-            pw.print("] ");
-            pw.print(formatTime(next));
+    static class DumpFilter {
+        private boolean mDumpCheckIn = false;
+        private boolean mCheckInClear = false;
 
-            pw.print("  Config:");
-            pw.print("    Max icon dim: ");
-            pw.println(mMaxIconDimension);
-            pw.print("    Icon format: ");
-            pw.println(mIconPersistFormat);
-            pw.print("    Icon quality: ");
-            pw.println(mIconPersistQuality);
-            pw.print("    saveDelayMillis: ");
-            pw.println(mSaveDelayMillis);
-            pw.print("    resetInterval: ");
-            pw.println(mResetInterval);
-            pw.print("    maxUpdatesPerInterval: ");
-            pw.println(mMaxUpdatesPerInterval);
-            pw.print("    maxShortcutsPerActivity: ");
-            pw.println(mMaxShortcuts);
-            pw.println();
+        private boolean mDumpMain = true;
+        private boolean mDumpUid = false;
+        private boolean mDumpFiles = false;
 
-            pw.println("  Stats:");
-            synchronized (mStatLock) {
-                for (int i = 0; i < Stats.COUNT; i++) {
-                    dumpStatLS(pw, "    ", i);
+        private boolean mDumpDetails = true;
+        private List<Pattern> mPackagePatterns = new ArrayList<>();
+        private List<Integer> mUsers = new ArrayList<>();
+
+        void addPackageRegex(String regex) {
+            mPackagePatterns.add(Pattern.compile(regex));
+        }
+
+        public void addPackage(String packageName) {
+            addPackageRegex(Pattern.quote(packageName));
+        }
+
+        void addUser(int userId) {
+            mUsers.add(userId);
+        }
+
+        boolean isPackageMatch(String packageName) {
+            if (mPackagePatterns.size() == 0) {
+                return true;
+            }
+            for (int i = 0; i < mPackagePatterns.size(); i++) {
+                if (mPackagePatterns.get(i).matcher(packageName).find()) {
+                    return true;
                 }
             }
+            return false;
+        }
 
-            pw.println();
-            pw.print("  #Failures: ");
-            pw.println(mWtfCount);
+        boolean isUserMatch(int userId) {
+            if (mUsers.size() == 0) {
+                return true;
+            }
+            for (int i = 0; i < mUsers.size(); i++) {
+                if (mUsers.get(i) == userId) {
+                    return true;
+                }
+            }
+            return false;
+        }
 
-            if (mLastWtfStacktrace != null) {
-                pw.print("  Last failure stack trace: ");
-                pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+        public boolean shouldDumpCheckIn() {
+            return mDumpCheckIn;
+        }
+
+        public void setDumpCheckIn(boolean dumpCheckIn) {
+            mDumpCheckIn = dumpCheckIn;
+        }
+
+        public boolean shouldCheckInClear() {
+            return mCheckInClear;
+        }
+
+        public void setCheckInClear(boolean checkInClear) {
+            mCheckInClear = checkInClear;
+        }
+
+        public boolean shouldDumpMain() {
+            return mDumpMain;
+        }
+
+        public void setDumpMain(boolean dumpMain) {
+            mDumpMain = dumpMain;
+        }
+
+        public boolean shouldDumpUid() {
+            return mDumpUid;
+        }
+
+        public void setDumpUid(boolean dumpUid) {
+            mDumpUid = dumpUid;
+        }
+
+        public boolean shouldDumpFiles() {
+            return mDumpFiles;
+        }
+
+        public void setDumpFiles(boolean dumpFiles) {
+            mDumpFiles = dumpFiles;
+        }
+
+        public boolean shouldDumpDetails() {
+            return mDumpDetails;
+        }
+
+        public void setDumpDetails(boolean dumpDetails) {
+            mDumpDetails = dumpDetails;
+        }
+    }
+
+    private void dumpInner(PrintWriter pw) {
+        dumpInner(pw, new DumpFilter());
+    }
+
+    private void dumpInner(PrintWriter pw, DumpFilter filter) {
+        synchronized (mLock) {
+            if (filter.shouldDumpDetails()) {
+                final long now = injectCurrentTimeMillis();
+                pw.print("Now: [");
+                pw.print(now);
+                pw.print("] ");
+                pw.print(formatTime(now));
+
+                pw.print("  Raw last reset: [");
+                pw.print(mRawLastResetTime);
+                pw.print("] ");
+                pw.print(formatTime(mRawLastResetTime));
+
+                final long last = getLastResetTimeLocked();
+                pw.print("  Last reset: [");
+                pw.print(last);
+                pw.print("] ");
+                pw.print(formatTime(last));
+
+                final long next = getNextResetTimeLocked();
+                pw.print("  Next reset: [");
+                pw.print(next);
+                pw.print("] ");
+                pw.print(formatTime(next));
+
+                pw.print("  Config:");
+                pw.print("    Max icon dim: ");
+                pw.println(mMaxIconDimension);
+                pw.print("    Icon format: ");
+                pw.println(mIconPersistFormat);
+                pw.print("    Icon quality: ");
+                pw.println(mIconPersistQuality);
+                pw.print("    saveDelayMillis: ");
+                pw.println(mSaveDelayMillis);
+                pw.print("    resetInterval: ");
+                pw.println(mResetInterval);
+                pw.print("    maxUpdatesPerInterval: ");
+                pw.println(mMaxUpdatesPerInterval);
+                pw.print("    maxShortcutsPerActivity: ");
+                pw.println(mMaxShortcuts);
+                pw.println();
+
+                pw.println("  Stats:");
+                synchronized (mStatLock) {
+                    for (int i = 0; i < Stats.COUNT; i++) {
+                        dumpStatLS(pw, "    ", i);
+                    }
+                }
+
+                pw.println();
+                pw.print("  #Failures: ");
+                pw.println(mWtfCount);
+
+                if (mLastWtfStacktrace != null) {
+                    pw.print("  Last failure stack trace: ");
+                    pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+                }
+
+                pw.println();
+                mShortcutBitmapSaver.dumpLocked(pw, "  ");
+
+                pw.println();
             }
 
-            pw.println();
-            mShortcutBitmapSaver.dumpLocked(pw, "  ");
-
             for (int i = 0; i < mUsers.size(); i++) {
-                pw.println();
-                mUsers.valueAt(i).dump(pw, "  ");
+                final ShortcutUser user = mUsers.valueAt(i);
+                if (filter.isUserMatch(user.getUserId())) {
+                    user.dump(pw, "  ", filter);
+                    pw.println();
+                }
             }
         }
     }
diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java
index 2c388c4..55e6d28 100644
--- a/com/android/server/pm/ShortcutUser.java
+++ b/com/android/server/pm/ShortcutUser.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutService.InvalidFileFormatException;
 
 import libcore.util.Objects;
@@ -531,44 +532,54 @@
                 + " S=" + restoredShortcuts[0]);
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-        pw.print(prefix);
-        pw.print("User: ");
-        pw.print(mUserId);
-        pw.print("  Known locales: ");
-        pw.print(mKnownLocales);
-        pw.print("  Last app scan: [");
-        pw.print(mLastAppScanTime);
-        pw.print("] ");
-        pw.print(ShortcutService.formatTime(mLastAppScanTime));
-        pw.print("  Last app scan FP: ");
-        pw.print(mLastAppScanOsFingerprint);
-        pw.println();
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
+        if (filter.shouldDumpDetails()) {
+            pw.print(prefix);
+            pw.print("User: ");
+            pw.print(mUserId);
+            pw.print("  Known locales: ");
+            pw.print(mKnownLocales);
+            pw.print("  Last app scan: [");
+            pw.print(mLastAppScanTime);
+            pw.print("] ");
+            pw.print(ShortcutService.formatTime(mLastAppScanTime));
+            pw.print("  Last app scan FP: ");
+            pw.print(mLastAppScanOsFingerprint);
+            pw.println();
 
-        prefix += prefix + "  ";
+            prefix += prefix + "  ";
 
-        pw.print(prefix);
-        pw.print("Cached launcher: ");
-        pw.print(mCachedLauncher);
-        pw.println();
+            pw.print(prefix);
+            pw.print("Cached launcher: ");
+            pw.print(mCachedLauncher);
+            pw.println();
 
-        pw.print(prefix);
-        pw.print("Last known launcher: ");
-        pw.print(mLastKnownLauncher);
-        pw.println();
+            pw.print(prefix);
+            pw.print("Last known launcher: ");
+            pw.print(mLastKnownLauncher);
+            pw.println();
+        }
 
         for (int i = 0; i < mLaunchers.size(); i++) {
-            mLaunchers.valueAt(i).dump(pw, prefix);
+            ShortcutLauncher launcher = mLaunchers.valueAt(i);
+            if (filter.isPackageMatch(launcher.getPackageName())) {
+                launcher.dump(pw, prefix, filter);
+            }
         }
 
         for (int i = 0; i < mPackages.size(); i++) {
-            mPackages.valueAt(i).dump(pw, prefix);
+            ShortcutPackage pkg = mPackages.valueAt(i);
+            if (filter.isPackageMatch(pkg.getPackageName())) {
+                pkg.dump(pw, prefix, filter);
+            }
         }
 
-        pw.println();
-        pw.print(prefix);
-        pw.println("Bitmap directories: ");
-        dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
+        if (filter.shouldDumpDetails()) {
+            pw.println();
+            pw.print(prefix);
+            pw.println("Bitmap directories: ");
+            dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
+        }
     }
 
     private void dumpDirectorySize(@NonNull PrintWriter pw,
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index f2d527b..1e5245c 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -1055,7 +1055,7 @@
 
     /** Called by PackageManagerService */
     public boolean exists(int userId) {
-        return getUserInfoNoChecks(userId) != null;
+        return mLocalService.exists(userId);
     }
 
     @Override
@@ -3502,8 +3502,8 @@
      * @param userId
      * @return whether the user has been initialized yet
      */
-    boolean isInitialized(int userId) {
-        return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+    boolean isUserInitialized(int userId) {
+        return mLocalService.isUserInitialized(userId);
     }
 
     private class LocalService extends UserManagerInternal {
@@ -3715,6 +3715,16 @@
             }
             return state == UserState.STATE_RUNNING_UNLOCKED;
         }
+
+        @Override
+        public boolean isUserInitialized(int userId) {
+            return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+        }
+
+        @Override
+        public boolean exists(int userId) {
+            return getUserInfoNoChecks(userId) != null;
+        }
     }
 
     /* Remove all the users except of the system one. */
diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java
new file mode 100644
index 0000000..09a6e9c
--- /dev/null
+++ b/com/android/server/pm/permission/BasePermission.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2006 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.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.content.pm.PermissionInfo.PROTECTION_NORMAL;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM;
+
+import static com.android.server.pm.Settings.ATTR_NAME;
+import static com.android.server.pm.Settings.ATTR_PACKAGE;
+import static com.android.server.pm.Settings.TAG_ITEM;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Permission;
+import android.content.pm.PermissionInfo;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSettingBase;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public final class BasePermission {
+    static final String TAG = "PackageManager";
+
+    public static final int TYPE_NORMAL = 0;
+    public static final int TYPE_BUILTIN = 1;
+    public static final int TYPE_DYNAMIC = 2;
+    @IntDef(value = {
+        TYPE_NORMAL,
+        TYPE_BUILTIN,
+        TYPE_DYNAMIC,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PermissionType {}
+
+    @IntDef(value = {
+        PROTECTION_DANGEROUS,
+        PROTECTION_NORMAL,
+        PROTECTION_SIGNATURE,
+        PROTECTION_SIGNATURE_OR_SYSTEM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProtectionLevel {}
+
+    final String name;
+
+    @PermissionType final int type;
+
+    String sourcePackageName;
+
+    // TODO: Can we get rid of this? Seems we only use some signature info from the setting
+    PackageSettingBase sourcePackageSetting;
+
+    int protectionLevel;
+
+    PackageParser.Permission perm;
+
+    PermissionInfo pendingPermissionInfo;
+
+    /** UID that owns the definition of this permission */
+    int uid;
+
+    /** Additional GIDs given to apps granted this permission */
+    private int[] gids;
+
+    /**
+     * Flag indicating that {@link #gids} should be adjusted based on the
+     * {@link UserHandle} the granted app is running as.
+     */
+    private boolean perUser;
+
+    public BasePermission(String _name, String _sourcePackageName, @PermissionType int _type) {
+        name = _name;
+        sourcePackageName = _sourcePackageName;
+        type = _type;
+        // Default to most conservative protection level.
+        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
+    }
+
+    @Override
+    public String toString() {
+        return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+                + "}";
+    }
+
+    public String getName() {
+        return name;
+    }
+    public int getProtectionLevel() {
+        return protectionLevel;
+    }
+    public String getSourcePackageName() {
+        return sourcePackageName;
+    }
+    public PackageSettingBase getSourcePackageSetting() {
+        return sourcePackageSetting;
+    }
+    public int getType() {
+        return type;
+    }
+    public int getUid() {
+        return uid;
+    }
+    public void setGids(int[] gids, boolean perUser) {
+        this.gids = gids;
+        this.perUser = perUser;
+    }
+    public void setPermission(@Nullable Permission perm) {
+        this.perm = perm;
+    }
+    public void setSourcePackageSetting(PackageSettingBase sourcePackageSetting) {
+        this.sourcePackageSetting = sourcePackageSetting;
+    }
+
+    public int[] computeGids(int userId) {
+        if (perUser) {
+            final int[] userGids = new int[gids.length];
+            for (int i = 0; i < gids.length; i++) {
+                userGids[i] = UserHandle.getUid(userId, gids[i]);
+            }
+            return userGids;
+        } else {
+            return gids;
+        }
+    }
+
+    public int calculateFootprint(BasePermission perm) {
+        if (uid == perm.uid) {
+            return perm.name.length() + perm.perm.info.calculateFootprint();
+        }
+        return 0;
+    }
+
+    public boolean isPermission(Permission perm) {
+        return this.perm == perm;
+    }
+
+    public boolean isDynamic() {
+        return type == TYPE_DYNAMIC;
+    }
+
+
+    public boolean isNormal() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+                == PermissionInfo.PROTECTION_NORMAL;
+    }
+    public boolean isRuntime() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+                == PermissionInfo.PROTECTION_DANGEROUS;
+    }
+    public boolean isSignature() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) ==
+                PermissionInfo.PROTECTION_SIGNATURE;
+    }
+
+    public boolean isAppOp() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+    }
+    public boolean isDevelopment() {
+        return isSignature()
+                && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
+    }
+    public boolean isInstaller() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0;
+    }
+    public boolean isInstant() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
+    }
+    public boolean isOEM() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
+    }
+    public boolean isPre23() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRE23) != 0;
+    }
+    public boolean isPreInstalled() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0;
+    }
+    public boolean isPrivileged() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
+    }
+    public boolean isRuntimeOnly() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
+    }
+    public boolean isSetup() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0;
+    }
+    public boolean isVerifier() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0;
+    }
+
+    public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
+        if (!origPackageName.equals(sourcePackageName)) {
+            return;
+        }
+        sourcePackageName = newPackageName;
+        sourcePackageSetting = null;
+        perm = null;
+        if (pendingPermissionInfo != null) {
+            pendingPermissionInfo.packageName = newPackageName;
+        }
+        uid = 0;
+        setGids(null, false);
+    }
+
+    public boolean addToTree(@ProtectionLevel int protectionLevel,
+            @NonNull PermissionInfo info, @NonNull BasePermission tree) {
+        final boolean changed =
+                (this.protectionLevel != protectionLevel
+                    || perm == null
+                    || uid != tree.uid
+                    || !perm.owner.equals(tree.perm.owner)
+                    || !comparePermissionInfos(perm.info, info));
+        this.protectionLevel = protectionLevel;
+        info = new PermissionInfo(info);
+        info.protectionLevel = protectionLevel;
+        perm = new PackageParser.Permission(tree.perm.owner, info);
+        perm.info.packageName = tree.perm.info.packageName;
+        uid = tree.uid;
+        return changed;
+    }
+
+    public void updateDynamicPermission(Map<String, 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);
+            if (tree != null && tree.perm != null) {
+                sourcePackageSetting = tree.sourcePackageSetting;
+                perm = new PackageParser.Permission(tree.perm.owner,
+                        new PermissionInfo(pendingPermissionInfo));
+                perm.info.packageName = tree.perm.info.packageName;
+                perm.info.name = name;
+                uid = tree.uid;
+            }
+        }
+    }
+
+    public static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
+            @NonNull PackageParser.Package pkg, Map<String, BasePermission> permissionTrees,
+            boolean chatty) {
+        final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras;
+        // Allow system apps to redefine non-system permissions
+        if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) {
+            final boolean currentOwnerIsSystem = (bp.perm != null
+                    && bp.perm.owner.isSystemApp());
+            if (p.owner.isSystemApp()) {
+                if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
+                    // It's a built-in permission and no owner, take ownership now
+                    bp.sourcePackageSetting = pkgSetting;
+                    bp.perm = p;
+                    bp.uid = pkg.applicationInfo.uid;
+                    bp.sourcePackageName = p.info.packageName;
+                    p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+                } else if (!currentOwnerIsSystem) {
+                    String msg = "New decl " + p.owner + " of permission  "
+                            + p.info.name + " is system; overriding " + bp.sourcePackageName;
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    bp = null;
+                }
+            }
+        }
+        if (bp == null) {
+            bp = new BasePermission(p.info.name, p.info.packageName, TYPE_NORMAL);
+        }
+        StringBuilder r = null;
+        if (bp.perm == null) {
+            if (bp.sourcePackageName == null
+                    || bp.sourcePackageName.equals(p.info.packageName)) {
+                final BasePermission tree = findPermissionTreeLP(permissionTrees, p.info.name);
+                if (tree == null
+                        || tree.sourcePackageName.equals(p.info.packageName)) {
+                    bp.sourcePackageSetting = pkgSetting;
+                    bp.perm = p;
+                    bp.uid = pkg.applicationInfo.uid;
+                    bp.sourcePackageName = p.info.packageName;
+                    p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+                    if (chatty) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append(p.info.name);
+                    }
+                } else {
+                    Slog.w(TAG, "Permission " + p.info.name + " from package "
+                            + p.info.packageName + " ignored: base tree "
+                            + tree.name + " is from package "
+                            + tree.sourcePackageName);
+                }
+            } else {
+                Slog.w(TAG, "Permission " + p.info.name + " from package "
+                        + p.info.packageName + " ignored: original from "
+                        + bp.sourcePackageName);
+            }
+        } else if (chatty) {
+            if (r == null) {
+                r = new StringBuilder(256);
+            } else {
+                r.append(' ');
+            }
+            r.append("DUP:");
+            r.append(p.info.name);
+        }
+        if (bp.perm == p) {
+            bp.protectionLevel = p.info.protectionLevel;
+        }
+        if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
+            Log.d(TAG, "  Permissions: " + r);
+        }
+        return bp;
+    }
+
+    public static BasePermission enforcePermissionTreeLP(
+            Map<String, BasePermission> permissionTrees, String permName, int callingUid) {
+        if (permName != null) {
+            BasePermission bp = findPermissionTreeLP(permissionTrees, permName);
+            if (bp != null) {
+                if (bp.uid == UserHandle.getAppId(callingUid)) {//UserHandle.getAppId(Binder.getCallingUid())) {
+                    return bp;
+                }
+                throw new SecurityException("Calling uid " + callingUid
+                        + " is not allowed to add to permission tree "
+                        + bp.name + " owned by uid " + bp.uid);
+            }
+        }
+        throw new SecurityException("No permission tree found for " + permName);
+    }
+
+    public void enforceDeclaredUsedAndRuntimeOrDevelopment(PackageParser.Package pkg) {
+        int index = pkg.requestedPermissions.indexOf(name);
+        if (index == -1) {
+            throw new SecurityException("Package " + pkg.packageName
+                    + " has not requested permission " + name);
+        }
+        if (!isRuntime() && !isDevelopment()) {
+            throw new SecurityException("Permission " + name
+                    + " is not a changeable permission type");
+        }
+    }
+
+    private static BasePermission findPermissionTreeLP(
+            Map<String, BasePermission> permissionTrees, String permName) {
+        for (BasePermission bp : permissionTrees.values()) {
+            if (permName.startsWith(bp.name) &&
+                    permName.length() > bp.name.length() &&
+                    permName.charAt(bp.name.length()) == '.') {
+                return bp;
+            }
+        }
+        return null;
+    }
+
+    public @Nullable PermissionInfo generatePermissionInfo(@NonNull String groupName, int flags) {
+        if (groupName == null) {
+            if (perm == null || perm.info.group == null) {
+                return generatePermissionInfo(protectionLevel, flags);
+            }
+        } else {
+            if (perm != null && groupName.equals(perm.info.group)) {
+                return PackageParser.generatePermissionInfo(perm, flags);
+            }
+        }
+        return null;
+    }
+
+    public @NonNull PermissionInfo generatePermissionInfo(int adjustedProtectionLevel, int flags) {
+        final boolean protectionLevelChanged = protectionLevel != adjustedProtectionLevel;
+        // if we return different protection level, don't use the cached info
+        if (perm != null && !protectionLevelChanged) {
+            return PackageParser.generatePermissionInfo(perm, flags);
+        }
+        final PermissionInfo pi = new PermissionInfo();
+        pi.name = name;
+        pi.packageName = sourcePackageName;
+        pi.nonLocalizedLabel = name;
+        pi.protectionLevel = protectionLevelChanged ? adjustedProtectionLevel : protectionLevel;
+        return pi;
+    }
+
+    public static boolean readLPw(@NonNull Map<String, BasePermission> out,
+            @NonNull XmlPullParser parser) {
+        final String tagName = parser.getName();
+        if (!tagName.equals(TAG_ITEM)) {
+            return false;
+        }
+        final String name = parser.getAttributeValue(null, ATTR_NAME);
+        final String sourcePackage = parser.getAttributeValue(null, ATTR_PACKAGE);
+        final String ptype = parser.getAttributeValue(null, "type");
+        if (name == null || sourcePackage == null) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: permissions has" + " no name at "
+                            + parser.getPositionDescription());
+            return false;
+        }
+        final boolean dynamic = "dynamic".equals(ptype);
+        BasePermission bp = out.get(name);
+        // If the permission is builtin, do not clobber it.
+        if (bp == null || bp.type != TYPE_BUILTIN) {
+            bp = new BasePermission(name.intern(), sourcePackage,
+                    dynamic ? TYPE_DYNAMIC : TYPE_NORMAL);
+        }
+        bp.protectionLevel = readInt(parser, null, "protection",
+                PermissionInfo.PROTECTION_NORMAL);
+        bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
+        if (dynamic) {
+            final PermissionInfo pi = new PermissionInfo();
+            pi.packageName = sourcePackage.intern();
+            pi.name = name.intern();
+            pi.icon = readInt(parser, null, "icon", 0);
+            pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
+            pi.protectionLevel = bp.protectionLevel;
+            bp.pendingPermissionInfo = pi;
+        }
+        out.put(bp.name, bp);
+        return true;
+    }
+
+    private static int readInt(XmlPullParser parser, String ns, String name, int defValue) {
+        String v = parser.getAttributeValue(ns, name);
+        try {
+            if (v == null) {
+                return defValue;
+            }
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: attribute " + name
+                            + " has bad integer value " + v + " at "
+                            + parser.getPositionDescription());
+        }
+        return defValue;
+    }
+
+    public void writeLPr(@NonNull XmlSerializer serializer) throws IOException {
+        if (sourcePackageName == null) {
+            return;
+        }
+        serializer.startTag(null, TAG_ITEM);
+        serializer.attribute(null, ATTR_NAME, name);
+        serializer.attribute(null, ATTR_PACKAGE, sourcePackageName);
+        if (protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
+            serializer.attribute(null, "protection", Integer.toString(protectionLevel));
+        }
+        if (type == BasePermission.TYPE_DYNAMIC) {
+            final PermissionInfo pi = perm != null ? perm.info : pendingPermissionInfo;
+            if (pi != null) {
+                serializer.attribute(null, "type", "dynamic");
+                if (pi.icon != 0) {
+                    serializer.attribute(null, "icon", Integer.toString(pi.icon));
+                }
+                if (pi.nonLocalizedLabel != null) {
+                    serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
+                }
+            }
+        }
+        serializer.endTag(null, TAG_ITEM);
+    }
+
+    private static boolean compareStrings(CharSequence s1, CharSequence s2) {
+        if (s1 == null) {
+            return s2 == null;
+        }
+        if (s2 == null) {
+            return false;
+        }
+        if (s1.getClass() != s2.getClass()) {
+            return false;
+        }
+        return s1.equals(s2);
+    }
+
+    private static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
+        if (pi1.icon != pi2.icon) return false;
+        if (pi1.logo != pi2.logo) return false;
+        if (pi1.protectionLevel != pi2.protectionLevel) return false;
+        if (!compareStrings(pi1.name, pi2.name)) return false;
+        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
+        // We'll take care of setting this one.
+        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
+        // These are not currently stored in settings.
+        //if (!compareStrings(pi1.group, pi2.group)) return false;
+        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
+        //if (pi1.labelRes != pi2.labelRes) return false;
+        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
+        return true;
+    }
+
+    public boolean dumpPermissionsLPr(@NonNull PrintWriter pw, @NonNull String packageName,
+            @NonNull Set<String> permissionNames, boolean readEnforced,
+            boolean printedSomething, @NonNull DumpState dumpState) {
+        if (packageName != null && !packageName.equals(sourcePackageName)) {
+            return false;
+        }
+        if (permissionNames != null && !permissionNames.contains(name)) {
+            return false;
+        }
+        if (!printedSomething) {
+            if (dumpState.onTitlePrinted())
+                pw.println();
+            pw.println("Permissions:");
+            printedSomething = true;
+        }
+        pw.print("  Permission ["); pw.print(name); pw.print("] (");
+                pw.print(Integer.toHexString(System.identityHashCode(this)));
+                pw.println("):");
+        pw.print("    sourcePackage="); pw.println(sourcePackageName);
+        pw.print("    uid="); pw.print(uid);
+                pw.print(" gids="); pw.print(Arrays.toString(
+                        computeGids(UserHandle.USER_SYSTEM)));
+                pw.print(" type="); pw.print(type);
+                pw.print(" prot=");
+                pw.println(PermissionInfo.protectionToString(protectionLevel));
+        if (perm != null) {
+            pw.print("    perm="); pw.println(perm);
+            if ((perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
+                    || (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
+                pw.print("    flags=0x"); pw.println(Integer.toHexString(perm.info.flags));
+            }
+        }
+        if (sourcePackageSetting != null) {
+            pw.print("    packageSetting="); pw.println(sourcePackageSetting);
+        }
+        if (READ_EXTERNAL_STORAGE.equals(name)) {
+            pw.print("    enforced=");
+            pw.println(readEnforced);
+        }
+        return true;
+    }
+}
diff --git a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
new file mode 100644
index 0000000..161efd3
--- /dev/null
+++ b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -0,0 +1,1296 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.DownloadManager;
+import android.app.admin.DevicePolicyManager;
+import android.companion.CompanionDeviceManager;
+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.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManagerInternal.PackagesProvider;
+import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.print.PrintManager;
+import android.provider.CalendarContract;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.TelephonyManager;
+import android.security.Credentials;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+import com.android.server.LocalServices;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSetting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+/**
+ * This class is the policy for granting runtime permissions to
+ * platform components and default handlers in the system such
+ * that the device is usable out-of-the-box. For example, the
+ * shell UID is a part of the system and the Phone app should
+ * have phone related permission by default.
+ * <p>
+ * NOTE: This class is at the wrong abstraction level. It is a part of the package manager
+ * service but knows about lots of higher level subsystems. The correct way to do this is
+ * to have an interface defined in the package manager but have the impl next to other
+ * policy stuff like PhoneWindowManager
+ */
+public final class DefaultPermissionGrantPolicy {
+    private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
+    private static final boolean DEBUG = false;
+
+    private static final int DEFAULT_FLAGS =
+            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
+    private static final String AUDIO_MIME_TYPE = "audio/mpeg";
+
+    private static final String TAG_EXCEPTIONS = "exceptions";
+    private static final String TAG_EXCEPTION = "exception";
+    private static final String TAG_PERMISSION = "permission";
+    private static final String ATTR_PACKAGE = "package";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_FIXED = "fixed";
+
+    private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
+    static {
+        PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
+        PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
+        PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
+        PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
+        PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
+        PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
+        PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
+    }
+
+    private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
+    static {
+        CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
+        CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
+        CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
+    }
+
+    private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
+    static {
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+    }
+
+    private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
+    static {
+        CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
+        CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
+    }
+
+    private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
+    static {
+        SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
+        SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
+    }
+
+    private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
+    static {
+        MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
+    }
+
+    private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
+    static {
+        CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
+    }
+
+    private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
+    static {
+        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+    }
+
+    private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
+    static {
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+    }
+
+    private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
+
+    private static final String ACTION_TRACK = "com.android.fitness.TRACK";
+
+    private final Handler mHandler;
+
+    private PackagesProvider mLocationPackagesProvider;
+    private PackagesProvider mVoiceInteractionPackagesProvider;
+    private PackagesProvider mSmsAppPackagesProvider;
+    private PackagesProvider mDialerAppPackagesProvider;
+    private PackagesProvider mSimCallManagerPackagesProvider;
+    private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
+
+    private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
+    private final Context mContext;
+    private final Object mLock = new Object();
+    private final PackageManagerInternal mServiceInternal;
+    private final PermissionManagerService mPermissionManager;
+    private final DefaultPermissionGrantedCallback mPermissionGrantedCallback;
+    public interface DefaultPermissionGrantedCallback {
+        /** Callback when permissions have been granted */
+        public void onDefaultRuntimePermissionsGranted(int userId);
+    }
+
+    public DefaultPermissionGrantPolicy(Context context, Looper looper,
+            @Nullable DefaultPermissionGrantedCallback callback,
+            @NonNull PermissionManagerService permissionManager) {
+        mContext = context;
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
+                    synchronized (mLock) {
+                        if (mGrantExceptions == null) {
+                            mGrantExceptions = readDefaultPermissionExceptionsLocked();
+                        }
+                    }
+                }
+            }
+        };
+        mPermissionGrantedCallback = callback;
+        mPermissionManager = permissionManager;
+        mServiceInternal = LocalServices.getService(PackageManagerInternal.class);
+    }
+
+    public void setLocationPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mLocationPackagesProvider = provider;
+        }
+    }
+
+    public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mVoiceInteractionPackagesProvider = provider;
+        }
+    }
+
+    public void setSmsAppPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mSmsAppPackagesProvider = provider;
+        }
+    }
+
+    public void setDialerAppPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mDialerAppPackagesProvider = provider;
+        }
+    }
+
+    public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mSimCallManagerPackagesProvider = provider;
+        }
+    }
+
+    public void setSyncAdapterPackagesProvider(SyncAdapterPackagesProvider provider) {
+        synchronized (mLock) {
+            mSyncAdapterPackagesProvider = provider;
+        }
+    }
+
+    public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
+            grantAllRuntimePermissions(packages, userId);
+        } else {
+            grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+            grantDefaultSystemHandlerPermissions(userId);
+            grantDefaultPermissionExceptions(userId);
+        }
+    }
+
+    private void grantRuntimePermissionsForPackage(int userId, PackageParser.Package pkg) {
+        Set<String> permissions = new ArraySet<>();
+        for (String permission :  pkg.requestedPermissions) {
+            final BasePermission bp = mPermissionManager.getPermission(permission);
+            if (bp == null) {
+                continue;
+            }
+            if (bp.isRuntime()) {
+                permissions.add(permission);
+            }
+        }
+        if (!permissions.isEmpty()) {
+            grantRuntimePermissions(pkg, permissions, true, userId);
+        }
+    }
+
+    private void grantAllRuntimePermissions(
+            Collection<PackageParser.Package> packages, int userId) {
+        Log.i(TAG, "Granting all runtime permissions for user " + userId);
+        for (PackageParser.Package pkg : packages) {
+            grantRuntimePermissionsForPackage(userId, pkg);
+        }
+    }
+
+    public void scheduleReadDefaultPermissionExceptions() {
+        mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+    }
+
+    private void grantPermissionsToSysComponentsAndPrivApps(
+            Collection<PackageParser.Package> packages, int userId) {
+        Log.i(TAG, "Granting permissions to platform components for user " + userId);
+        for (PackageParser.Package pkg : packages) {
+            if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
+                    || !doesPackageSupportRuntimePermissions(pkg)
+                    || pkg.requestedPermissions.isEmpty()) {
+                continue;
+            }
+            grantRuntimePermissionsForPackage(userId, pkg);
+        }
+    }
+
+    private void grantDefaultSystemHandlerPermissions(int userId) {
+        Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
+
+        final PackagesProvider locationPackagesProvider;
+        final PackagesProvider voiceInteractionPackagesProvider;
+        final PackagesProvider smsAppPackagesProvider;
+        final PackagesProvider dialerAppPackagesProvider;
+        final PackagesProvider simCallManagerPackagesProvider;
+        final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
+
+        synchronized (mLock) {
+            locationPackagesProvider = mLocationPackagesProvider;
+            voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
+            smsAppPackagesProvider = mSmsAppPackagesProvider;
+            dialerAppPackagesProvider = mDialerAppPackagesProvider;
+            simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
+            syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
+        }
+
+        String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
+                ? voiceInteractionPackagesProvider.getPackages(userId) : null;
+        String[] locationPackageNames = (locationPackagesProvider != null)
+                ? locationPackagesProvider.getPackages(userId) : null;
+        String[] smsAppPackageNames = (smsAppPackagesProvider != null)
+                ? smsAppPackagesProvider.getPackages(userId) : null;
+        String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
+                ? dialerAppPackagesProvider.getPackages(userId) : null;
+        String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
+                ? simCallManagerPackagesProvider.getPackages(userId) : null;
+        String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+                syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
+        String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+                syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
+
+        // Installer
+        final String installerPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_INSTALLER, userId);
+        PackageParser.Package installerPackage = getSystemPackage(installerPackageName);
+        if (installerPackage != null
+                && doesPackageSupportRuntimePermissions(installerPackage)) {
+            grantRuntimePermissions(installerPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Verifier
+        final String verifierPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_VERIFIER, userId);
+        PackageParser.Package verifierPackage = getSystemPackage(verifierPackageName);
+        if (verifierPackage != null
+                && doesPackageSupportRuntimePermissions(verifierPackage)) {
+            grantRuntimePermissions(verifierPackage, STORAGE_PERMISSIONS, true, userId);
+            grantRuntimePermissions(verifierPackage, PHONE_PERMISSIONS, false, userId);
+            grantRuntimePermissions(verifierPackage, SMS_PERMISSIONS, false, userId);
+        }
+
+        // SetupWizard
+        final String setupWizardPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId);
+        PackageParser.Package setupPackage = getSystemPackage(setupWizardPackageName);
+        if (setupPackage != null
+                && doesPackageSupportRuntimePermissions(setupPackage)) {
+            grantRuntimePermissions(setupPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, LOCATION_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, CAMERA_PERMISSIONS, userId);
+        }
+
+        // Camera
+        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+        PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackage(
+                cameraIntent, userId);
+        if (cameraPackage != null
+                && doesPackageSupportRuntimePermissions(cameraPackage)) {
+            grantRuntimePermissions(cameraPackage, CAMERA_PERMISSIONS, userId);
+            grantRuntimePermissions(cameraPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(cameraPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Media provider
+        PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackage(
+                MediaStore.AUTHORITY, userId);
+        if (mediaStorePackage != null) {
+            grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Downloads provider
+        PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackage(
+                "downloads", userId);
+        if (downloadsPackage != null) {
+            grantRuntimePermissions(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Downloads UI
+        Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+        PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackage(
+                downloadsUiIntent, userId);
+        if (downloadsUiPackage != null
+                && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
+            grantRuntimePermissions(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Storage provider
+        PackageParser.Package storagePackage = getDefaultProviderAuthorityPackage(
+                "com.android.externalstorage.documents", userId);
+        if (storagePackage != null) {
+            grantRuntimePermissions(storagePackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // CertInstaller
+        Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
+        PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackage(
+                certInstallerIntent, userId);
+        if (certInstallerPackage != null
+                && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
+            grantRuntimePermissions(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Dialer
+        if (dialerAppPackageNames == null) {
+            Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
+            PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackage(
+                    dialerIntent, userId);
+            if (dialerPackage != null) {
+                grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+            }
+        } else {
+            for (String dialerAppPackageName : dialerAppPackageNames) {
+                PackageParser.Package dialerPackage = getSystemPackage(dialerAppPackageName);
+                if (dialerPackage != null) {
+                    grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+                }
+            }
+        }
+
+        // Sim call manager
+        if (simCallManagerPackageNames != null) {
+            for (String simCallManagerPackageName : simCallManagerPackageNames) {
+                PackageParser.Package simCallManagerPackage =
+                        getSystemPackage(simCallManagerPackageName);
+                if (simCallManagerPackage != null) {
+                    grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage,
+                            userId);
+                }
+            }
+        }
+
+        // SMS
+        if (smsAppPackageNames == null) {
+            Intent smsIntent = new Intent(Intent.ACTION_MAIN);
+            smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
+            PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackage(
+                    smsIntent, userId);
+            if (smsPackage != null) {
+               grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+            }
+        } else {
+            for (String smsPackageName : smsAppPackageNames) {
+                PackageParser.Package smsPackage = getSystemPackage(smsPackageName);
+                if (smsPackage != null) {
+                    grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+                }
+            }
+        }
+
+        // Cell Broadcast Receiver
+        Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+        PackageParser.Package cbrPackage =
+                getDefaultSystemHandlerActivityPackage(cbrIntent, userId);
+        if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
+            grantRuntimePermissions(cbrPackage, SMS_PERMISSIONS, userId);
+        }
+
+        // Carrier Provisioning Service
+        Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
+        PackageParser.Package carrierProvPackage =
+                getDefaultSystemHandlerServicePackage(carrierProvIntent, userId);
+        if (carrierProvPackage != null
+                && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
+            grantRuntimePermissions(carrierProvPackage, SMS_PERMISSIONS, false, userId);
+        }
+
+        // Calendar
+        Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
+        calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
+        PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackage(
+                calendarIntent, userId);
+        if (calendarPackage != null
+                && doesPackageSupportRuntimePermissions(calendarPackage)) {
+            grantRuntimePermissions(calendarPackage, CALENDAR_PERMISSIONS, userId);
+            grantRuntimePermissions(calendarPackage, CONTACTS_PERMISSIONS, userId);
+        }
+
+        // Calendar provider
+        PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackage(
+                CalendarContract.AUTHORITY, userId);
+        if (calendarProviderPackage != null) {
+            grantRuntimePermissions(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(calendarProviderPackage, CALENDAR_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Calendar provider sync adapters
+        List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackages(
+                calendarSyncAdapterPackages, userId);
+        final int calendarSyncAdapterCount = calendarSyncAdapters.size();
+        for (int i = 0; i < calendarSyncAdapterCount; i++) {
+            PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
+            if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
+                grantRuntimePermissions(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
+            }
+        }
+
+        // Contacts
+        Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
+        contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
+        PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackage(
+                contactsIntent, userId);
+        if (contactsPackage != null
+                && doesPackageSupportRuntimePermissions(contactsPackage)) {
+            grantRuntimePermissions(contactsPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(contactsPackage, PHONE_PERMISSIONS, userId);
+        }
+
+        // Contacts provider sync adapters
+        List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackages(
+                contactsSyncAdapterPackages, userId);
+        final int contactsSyncAdapterCount = contactsSyncAdapters.size();
+        for (int i = 0; i < contactsSyncAdapterCount; i++) {
+            PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
+            if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
+                grantRuntimePermissions(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
+            }
+        }
+
+        // Contacts provider
+        PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackage(
+                ContactsContract.AUTHORITY, userId);
+        if (contactsProviderPackage != null) {
+            grantRuntimePermissions(contactsProviderPackage, CONTACTS_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(contactsProviderPackage, PHONE_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Device provisioning
+        Intent deviceProvisionIntent = new Intent(
+                DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
+        PackageParser.Package deviceProvisionPackage =
+                getDefaultSystemHandlerActivityPackage(deviceProvisionIntent, userId);
+        if (deviceProvisionPackage != null
+                && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
+            grantRuntimePermissions(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
+        }
+
+        // Maps
+        Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
+        mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
+        PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackage(
+                mapsIntent, userId);
+        if (mapsPackage != null
+                && doesPackageSupportRuntimePermissions(mapsPackage)) {
+            grantRuntimePermissions(mapsPackage, LOCATION_PERMISSIONS, userId);
+        }
+
+        // Gallery
+        Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
+        galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
+        PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackage(
+                galleryIntent, userId);
+        if (galleryPackage != null
+                && doesPackageSupportRuntimePermissions(galleryPackage)) {
+            grantRuntimePermissions(galleryPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Email
+        Intent emailIntent = new Intent(Intent.ACTION_MAIN);
+        emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
+        PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackage(
+                emailIntent, userId);
+        if (emailPackage != null
+                && doesPackageSupportRuntimePermissions(emailPackage)) {
+            grantRuntimePermissions(emailPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(emailPackage, CALENDAR_PERMISSIONS, userId);
+        }
+
+        // Browser
+        PackageParser.Package browserPackage = null;
+        String defaultBrowserPackage = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_BROWSER, userId);
+        if (defaultBrowserPackage != null) {
+            browserPackage = getPackage(defaultBrowserPackage);
+        }
+        if (browserPackage == null) {
+            Intent browserIntent = new Intent(Intent.ACTION_MAIN);
+            browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
+            browserPackage = getDefaultSystemHandlerActivityPackage(
+                    browserIntent, userId);
+        }
+        if (browserPackage != null
+                && doesPackageSupportRuntimePermissions(browserPackage)) {
+            grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, userId);
+        }
+
+        // Voice interaction
+        if (voiceInteractPackageNames != null) {
+            for (String voiceInteractPackageName : voiceInteractPackageNames) {
+                PackageParser.Package voiceInteractPackage = getSystemPackage(
+                        voiceInteractPackageName);
+                if (voiceInteractPackage != null
+                        && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
+                    grantRuntimePermissions(voiceInteractPackage,
+                            CONTACTS_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            CALENDAR_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            MICROPHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            PHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            SMS_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            LOCATION_PERMISSIONS, userId);
+                }
+            }
+        }
+
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            // Allow voice search on low-ram devices
+            Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
+            PackageParser.Package globalSearchPickerPackage =
+                getDefaultSystemHandlerActivityPackage(globalSearchIntent, userId);
+
+            if (globalSearchPickerPackage != null
+                    && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
+                grantRuntimePermissions(globalSearchPickerPackage,
+                    MICROPHONE_PERMISSIONS, true, userId);
+                grantRuntimePermissions(globalSearchPickerPackage,
+                    LOCATION_PERMISSIONS, true, userId);
+            }
+        }
+
+        // Voice recognition
+        Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
+        voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackage(
+                voiceRecoIntent, userId);
+        if (voiceRecoPackage != null
+                && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
+            grantRuntimePermissions(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
+        }
+
+        // Location
+        if (locationPackageNames != null) {
+            for (String packageName : locationPackageNames) {
+                PackageParser.Package locationPackage = getSystemPackage(packageName);
+                if (locationPackage != null
+                        && doesPackageSupportRuntimePermissions(locationPackage)) {
+                    grantRuntimePermissions(locationPackage, CONTACTS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, CALENDAR_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, MICROPHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, PHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, SMS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, LOCATION_PERMISSIONS,
+                            true, userId);
+                    grantRuntimePermissions(locationPackage, CAMERA_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, SENSORS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, STORAGE_PERMISSIONS, userId);
+                }
+            }
+        }
+
+        // Music
+        Intent musicIntent = new Intent(Intent.ACTION_VIEW);
+        musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
+                AUDIO_MIME_TYPE);
+        PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackage(
+                musicIntent, userId);
+        if (musicPackage != null
+                && doesPackageSupportRuntimePermissions(musicPackage)) {
+            grantRuntimePermissions(musicPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Home
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
+        PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackage(
+                homeIntent, userId);
+        if (homePackage != null
+                && doesPackageSupportRuntimePermissions(homePackage)) {
+            grantRuntimePermissions(homePackage, LOCATION_PERMISSIONS, false, userId);
+        }
+
+        // Watches
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
+            // Home application on watches
+            Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
+            wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
+
+            PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackage(
+                    wearHomeIntent, userId);
+
+            if (wearHomePackage != null
+                    && doesPackageSupportRuntimePermissions(wearHomePackage)) {
+                grantRuntimePermissions(wearHomePackage, CONTACTS_PERMISSIONS, false,
+                        userId);
+                grantRuntimePermissions(wearHomePackage, PHONE_PERMISSIONS, true, userId);
+                grantRuntimePermissions(wearHomePackage, MICROPHONE_PERMISSIONS, false,
+                        userId);
+                grantRuntimePermissions(wearHomePackage, LOCATION_PERMISSIONS, false,
+                        userId);
+            }
+
+            // Fitness tracking on watches
+            Intent trackIntent = new Intent(ACTION_TRACK);
+            PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackage(
+                    trackIntent, userId);
+            if (trackPackage != null
+                    && doesPackageSupportRuntimePermissions(trackPackage)) {
+                grantRuntimePermissions(trackPackage, SENSORS_PERMISSIONS, false, userId);
+                grantRuntimePermissions(trackPackage, LOCATION_PERMISSIONS, false, userId);
+            }
+        }
+
+        // Print Spooler
+        PackageParser.Package printSpoolerPackage = getSystemPackage(
+                PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
+        if (printSpoolerPackage != null
+                && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
+            grantRuntimePermissions(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
+        }
+
+        // EmergencyInfo
+        Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
+        PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackage(
+                emergencyInfoIntent, userId);
+        if (emergencyInfoPckg != null
+                && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
+            grantRuntimePermissions(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
+            grantRuntimePermissions(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
+        }
+
+        // NFC Tag viewer
+        Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
+        nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
+        PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackage(
+                nfcTagIntent, userId);
+        if (nfcTagPkg != null
+                && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
+            grantRuntimePermissions(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
+            grantRuntimePermissions(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
+        }
+
+        // Storage Manager
+        Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
+        PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackage(
+                storageManagerIntent, userId);
+        if (storageManagerPckg != null
+                && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
+            grantRuntimePermissions(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Companion devices
+        PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackage(
+                CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
+        if (companionDeviceDiscoveryPackage != null
+                && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
+            grantRuntimePermissions(companionDeviceDiscoveryPackage,
+                    LOCATION_PERMISSIONS, true, userId);
+        }
+
+        // Ringtone Picker
+        Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+        PackageParser.Package ringtonePickerPackage =
+                getDefaultSystemHandlerActivityPackage(ringtonePickerIntent, userId);
+        if (ringtonePickerPackage != null
+                && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
+            grantRuntimePermissions(ringtonePickerPackage,
+                    STORAGE_PERMISSIONS, true, userId);
+        }
+
+        if (mPermissionGrantedCallback != null) {
+            mPermissionGrantedCallback.onDefaultRuntimePermissionsGranted(userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSystemDialerApp(
+            PackageParser.Package dialerPackage, int userId) {
+        if (doesPackageSupportRuntimePermissions(dialerPackage)) {
+            boolean isPhonePermFixed =
+                    mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
+            grantRuntimePermissions(
+                    dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
+            grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSystemSmsApp(
+            PackageParser.Package smsPackage, int userId) {
+        if (doesPackageSupportRuntimePermissions(smsPackage)) {
+            grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package smsPackage = getPackage(packageName);
+        if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
+            grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package dialerPackage = getPackage(packageName);
+        if (dialerPackage != null
+                && doesPackageSupportRuntimePermissions(dialerPackage)) {
+            grantRuntimePermissions(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSimCallManager(
+            PackageParser.Package simCallManagerPackage, int userId) {
+        Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
+        if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
+            grantRuntimePermissions(simCallManagerPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package simCallManagerPackage = getPackage(packageName);
+        if (simCallManagerPackage != null) {
+            grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId) {
+        Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
+        if (packageNames == null) {
+            return;
+        }
+        for (String packageName : packageNames) {
+            PackageParser.Package carrierPackage = getSystemPackage(packageName);
+            if (carrierPackage != null
+                    && doesPackageSupportRuntimePermissions(carrierPackage)) {
+                grantRuntimePermissions(carrierPackage, PHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(carrierPackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissions(carrierPackage, SMS_PERMISSIONS, userId);
+            }
+        }
+    }
+
+    public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId) {
+        Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
+        if (packageNames == null) {
+            return;
+        }
+        for (String packageName : packageNames) {
+            PackageParser.Package imsServicePackage = getSystemPackage(packageName);
+            if (imsServicePackage != null
+                    && doesPackageSupportRuntimePermissions(imsServicePackage)) {
+                grantRuntimePermissions(imsServicePackage, PHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, CAMERA_PERMISSIONS, userId);
+            }
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default browser for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package browserPackage = getSystemPackage(packageName);
+        if (browserPackage != null
+                && doesPackageSupportRuntimePermissions(browserPackage)) {
+            grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
+        }
+    }
+
+    private PackageParser.Package getDefaultSystemHandlerActivityPackage(
+            Intent intent, int userId) {
+        ResolveInfo handler = mServiceInternal.resolveIntent(intent,
+                intent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS, userId, false);
+        if (handler == null || handler.activityInfo == null) {
+            return null;
+        }
+        if (mServiceInternal.isResolveActivityComponent(handler.activityInfo)) {
+            return null;
+        }
+        return getSystemPackage(handler.activityInfo.packageName);
+    }
+
+    private PackageParser.Package getDefaultSystemHandlerServicePackage(
+            Intent intent, int userId) {
+        List<ResolveInfo> handlers = mServiceInternal.queryIntentServices(
+                intent, DEFAULT_FLAGS, Binder.getCallingUid(), userId);
+        if (handlers == null) {
+            return null;
+        }
+        final int handlerCount = handlers.size();
+        for (int i = 0; i < handlerCount; i++) {
+            ResolveInfo handler = handlers.get(i);
+            PackageParser.Package handlerPackage = getSystemPackage(
+                    handler.serviceInfo.packageName);
+            if (handlerPackage != null) {
+                return handlerPackage;
+            }
+        }
+        return null;
+    }
+
+    private List<PackageParser.Package> getHeadlessSyncAdapterPackages(
+            String[] syncAdapterPackageNames, int userId) {
+        List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
+
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+        for (String syncAdapterPackageName : syncAdapterPackageNames) {
+            homeIntent.setPackage(syncAdapterPackageName);
+
+            ResolveInfo homeActivity = mServiceInternal.resolveIntent(homeIntent,
+                    homeIntent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS,
+                    userId, false);
+            if (homeActivity != null) {
+                continue;
+            }
+
+            PackageParser.Package syncAdapterPackage = getSystemPackage(syncAdapterPackageName);
+            if (syncAdapterPackage != null) {
+                syncAdapterPackages.add(syncAdapterPackage);
+            }
+        }
+
+        return syncAdapterPackages;
+    }
+
+    private PackageParser.Package getDefaultProviderAuthorityPackage(
+            String authority, int userId) {
+        ProviderInfo provider =
+                mServiceInternal.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
+        if (provider != null) {
+            return getSystemPackage(provider.packageName);
+        }
+        return null;
+    }
+
+    private PackageParser.Package getPackage(String packageName) {
+        return mServiceInternal.getPackage(packageName);
+    }
+
+    private PackageParser.Package getSystemPackage(String packageName) {
+        PackageParser.Package pkg = getPackage(packageName);
+        if (pkg != null && pkg.isSystemApp()) {
+            return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null;
+        }
+        return null;
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            int userId) {
+        grantRuntimePermissions(pkg, permissions, false, false, userId);
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            boolean systemFixed, int userId) {
+        grantRuntimePermissions(pkg, permissions, systemFixed, false, userId);
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
+        if (pkg.requestedPermissions.isEmpty()) {
+            return;
+        }
+
+        List<String> requestedPermissions = pkg.requestedPermissions;
+        Set<String> grantablePermissions = null;
+
+        // If this is the default Phone or SMS app we grant permissions regardless
+        // whether the version on the system image declares the permission as used since
+        // selecting the app as the default Phone or SMS the user makes a deliberate
+        // choice to grant this app the permissions needed to function. For all other
+        // apps, (default grants on first boot and user creation) we don't grant default
+        // permissions if the version on the system image does not declare them.
+        if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
+            final PackageParser.Package disabledPkg =
+                    mServiceInternal.getDisabledPackage(pkg.packageName);
+            if (disabledPkg != null) {
+                if (disabledPkg.requestedPermissions.isEmpty()) {
+                    return;
+                }
+                if (!requestedPermissions.equals(disabledPkg.requestedPermissions)) {
+                    grantablePermissions = new ArraySet<>(requestedPermissions);
+                    requestedPermissions = disabledPkg.requestedPermissions;
+                }
+            }
+        }
+
+        final int grantablePermissionCount = requestedPermissions.size();
+        for (int i = 0; i < grantablePermissionCount; i++) {
+            String permission = requestedPermissions.get(i);
+
+            // If there is a disabled system app it may request a permission the updated
+            // version ot the data partition doesn't, In this case skip the permission.
+            if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
+                continue;
+            }
+
+            if (permissions.contains(permission)) {
+                final int flags = mServiceInternal.getPermissionFlagsTEMP(
+                        permission, pkg.packageName, userId);
+
+                // If any flags are set to the permission, then it is either set in
+                // its current state by the system or device/profile owner or the user.
+                // In all these cases we do not want to clobber the current state.
+                // Unless the caller wants to override user choices. The override is
+                // to make sure we can grant the needed permission to the default
+                // sms and phone apps after the user chooses this in the UI.
+                if (flags == 0 || isDefaultPhoneOrSms) {
+                    // Never clobber policy or system.
+                    final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                            | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                    if ((flags & fixedFlags) != 0) {
+                        continue;
+                    }
+
+                    mServiceInternal.grantRuntimePermission(
+                            pkg.packageName, permission, userId, false);
+                    if (DEBUG) {
+                        Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
+                                + permission + " to default handler " + pkg.packageName);
+                    }
+
+                    int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+                    if (systemFixed) {
+                        newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+                    }
+
+                    mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+                            newFlags, newFlags, userId);
+                }
+
+                // If a component gets a permission for being the default handler A
+                // and also default handler B, we grant the weaker grant form.
+                if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
+                        && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+                        && !systemFixed) {
+                    if (DEBUG) {
+                        Log.i(TAG, "Granted not fixed " + permission + " to default handler "
+                                + pkg.packageName);
+                    }
+                    mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+                            PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
+                }
+            }
+        }
+    }
+
+    private boolean isSysComponentOrPersistentPlatformSignedPrivApp(PackageParser.Package pkg) {
+        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
+            return true;
+        }
+        if (!pkg.isPrivilegedApp()) {
+            return false;
+        }
+        final PackageParser.Package disabledPkg =
+                mServiceInternal.getDisabledPackage(pkg.packageName);
+        if (disabledPkg != null) {
+            if ((disabledPkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+                return false;
+            }
+        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+            return false;
+        }
+        final String systemPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+        final PackageParser.Package systemPackage = getPackage(systemPackageName);
+        return PackageManagerService.compareSignatures(systemPackage.mSignatures,
+                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+    }
+
+    private void grantDefaultPermissionExceptions(int userId) {
+        mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+
+        synchronized (mLock) {
+            // mGrantExceptions is null only before the first read and then
+            // it serves as a cache of the default grants that should be
+            // performed for every user. If there is an entry then the app
+            // is on the system image and supports runtime permissions.
+            if (mGrantExceptions == null) {
+                mGrantExceptions = readDefaultPermissionExceptionsLocked();
+            }
+        }
+
+        Set<String> permissions = null;
+        final int exceptionCount = mGrantExceptions.size();
+        for (int i = 0; i < exceptionCount; i++) {
+            String packageName = mGrantExceptions.keyAt(i);
+            PackageParser.Package pkg = getSystemPackage(packageName);
+            List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
+            final int permissionGrantCount = permissionGrants.size();
+            for (int j = 0; j < permissionGrantCount; j++) {
+                DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
+                if (permissions == null) {
+                    permissions = new ArraySet<>();
+                } else {
+                    permissions.clear();
+                }
+                permissions.add(permissionGrant.name);
+                grantRuntimePermissions(pkg, permissions,
+                        permissionGrant.fixed, userId);
+            }
+        }
+    }
+
+    private File[] getDefaultPermissionFiles() {
+        ArrayList<File> ret = new ArrayList<File>();
+        File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
+        if (dir.isDirectory() && dir.canRead()) {
+            Collections.addAll(ret, dir.listFiles());
+        }
+        dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
+        if (dir.isDirectory() && dir.canRead()) {
+            Collections.addAll(ret, dir.listFiles());
+        }
+        return ret.isEmpty() ? null : ret.toArray(new File[0]);
+    }
+
+    private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
+            readDefaultPermissionExceptionsLocked() {
+        File[] files = getDefaultPermissionFiles();
+        if (files == null) {
+            return new ArrayMap<>(0);
+        }
+
+        ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
+
+        // Iterate over the files in the directory and scan .xml files
+        for (File file : files) {
+            if (!file.getPath().endsWith(".xml")) {
+                Slog.i(TAG, "Non-xml file " + file
+                        + " in " + file.getParent() + " directory, ignoring");
+                continue;
+            }
+            if (!file.canRead()) {
+                Slog.w(TAG, "Default permissions file " + file + " cannot be read");
+                continue;
+            }
+            try (
+                InputStream str = new BufferedInputStream(new FileInputStream(file))
+            ) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(str, null);
+                parse(parser, grantExceptions);
+            } catch (XmlPullParserException | IOException e) {
+                Slog.w(TAG, "Error reading default permissions file " + file, e);
+            }
+        }
+
+        return grantExceptions;
+    }
+
+    private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+            outGrantExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            if (TAG_EXCEPTIONS.equals(parser.getName())) {
+                parseExceptions(parser, outGrantExceptions);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName());
+            }
+        }
+    }
+
+    private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+            outGrantExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            if (TAG_EXCEPTION.equals(parser.getName())) {
+                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+
+                List<DefaultPermissionGrant> packageExceptions =
+                        outGrantExceptions.get(packageName);
+                if (packageExceptions == null) {
+                    // The package must be on the system image
+                    PackageParser.Package pkg = getSystemPackage(packageName);
+                    if (pkg == null) {
+                        Log.w(TAG, "Unknown package:" + packageName);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+
+                    // The package must support runtime permissions
+                    if (!doesPackageSupportRuntimePermissions(pkg)) {
+                        Log.w(TAG, "Skipping non supporting runtime permissions package:"
+                                + packageName);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    packageExceptions = new ArrayList<>();
+                    outGrantExceptions.put(packageName, packageExceptions);
+                }
+
+                parsePermission(parser, packageExceptions);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
+            }
+        }
+    }
+
+    private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
+            outPackageExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (TAG_PERMISSION.contains(parser.getName())) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                if (name == null) {
+                    Log.w(TAG, "Mandatory name attribute missing for permission tag");
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+
+                final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
+
+                DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
+                outPackageExceptions.add(exception);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
+            }
+        }
+    }
+
+    private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
+        return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
+    }
+
+    private static final class DefaultPermissionGrant {
+        final String name;
+        final boolean fixed;
+
+        public DefaultPermissionGrant(String name, boolean fixed) {
+            this.name = name;
+            this.fixed = fixed;
+        }
+    }
+}
diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java
new file mode 100644
index 0000000..3b20b42
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -0,0 +1,140 @@
+/*
+ * 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.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PermissionInfoFlags;
+import android.content.pm.PackageParser.Permission;
+
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Internal interfaces to be used by other components within the system server.
+ */
+public abstract class PermissionManagerInternal {
+    /**
+     * Callbacks invoked when interesting actions have been taken on a permission.
+     * <p>
+     * NOTE: The current arguments are merely to support the existing use cases. This
+     * needs to be properly thought out with appropriate arguments for each of the
+     * callback methods.
+     */
+    public static class PermissionCallback {
+        public void onGidsChanged(int appId, int userId) {
+        }
+        public void onPermissionChanged() {
+        }
+        public void onPermissionGranted(int uid, int userId) {
+        }
+        public void onInstallPermissionGranted() {
+        }
+        public void onPermissionRevoked(int uid, int userId) {
+        }
+        public void onInstallPermissionRevoked() {
+        }
+        public void onPermissionUpdated(int userId) {
+        }
+        public void onPermissionRemoved() {
+        }
+        public void onInstallPermissionUpdated() {
+        }
+    }
+
+    public abstract void grantRuntimePermission(
+            @NonNull String permName, @NonNull String packageName, boolean overridePolicy,
+            int callingUid, int userId, @Nullable PermissionCallback callback);
+    public abstract void grantRuntimePermissionsGrantedToDisabledPackage(
+            @NonNull PackageParser.Package pkg, int callingUid,
+            @Nullable PermissionCallback callback);
+    public abstract void grantRequestedRuntimePermissions(
+            @NonNull PackageParser.Package pkg, @NonNull int[] userIds,
+            @NonNull String[] grantedPermissions, int callingUid,
+            @Nullable PermissionCallback callback);
+    public abstract void revokeRuntimePermission(@NonNull String permName,
+            @NonNull String packageName, boolean overridePolicy, int callingUid, int userId,
+            @Nullable PermissionCallback callback);
+    public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting,
+            @NonNull int[] allUserIds);
+
+
+    public abstract boolean addPermission(@NonNull PermissionInfo info, boolean async,
+            int callingUid, @Nullable PermissionCallback callback);
+    public abstract void removePermission(@NonNull String permName, int callingUid,
+            @Nullable PermissionCallback callback);
+
+    public abstract int getPermissionFlags(@NonNull String permName,
+            @NonNull String packageName, int callingUid, int userId);
+    /**
+     * Retrieve all of the information we know about a particular permission.
+     */
+    public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName,
+            @NonNull String packageName, @PermissionInfoFlags int flags, int callingUid);
+    /**
+     * Retrieve all of the permissions associated with a particular group.
+     */
+    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
+     * the specified mask with the provided flag values.
+     */
+    public abstract void updatePermissionFlags(@NonNull String permName,
+            @NonNull String packageName, int flagMask, int flagValues, int callingUid, int userId,
+            @Nullable PermissionCallback callback);
+    /**
+     * Updates the flags for all applications by replacing the flags in the specified mask
+     * with the provided flag values.
+     */
+    public abstract boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues,
+            int callingUid, int userId, @NonNull Collection<PackageParser.Package> packages,
+            @Nullable PermissionCallback callback);
+
+    public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
+            int callingUid, int userId);
+
+    /**
+     * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userid} is not for the caller.
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    public abstract void enforceCrossUserPermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, @NonNull String message);
+    public abstract void enforceGrantRevokeRuntimePermissionPermissions(@NonNull String message);
+
+    public abstract @NonNull PermissionSettings getPermissionSettings();
+    public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy();
+
+    /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
+    public abstract Iterator<BasePermission> getPermissionIteratorTEMP();
+    public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
+    public abstract void putPermissionTEMP(@NonNull String permName,
+            @NonNull BasePermission permission);
+}
\ No newline at end of file
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
new file mode 100644
index 0000000..6c031a6
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -0,0 +1,1081 @@
+/*
+ * 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.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageParser.Package;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.os.storage.StorageManagerInternal;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
+import com.android.server.Watchdog;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.ProcessLoggingHandler;
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
+
+import libcore.util.EmptyArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Manages all permissions and handles permissions related tasks.
+ */
+public class PermissionManagerService {
+    private static final String TAG = "PackageManager";
+
+    /** All dangerous permission names in the same order as the events in MetricsEvent */
+    private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
+            Manifest.permission.READ_CALENDAR,
+            Manifest.permission.WRITE_CALENDAR,
+            Manifest.permission.CAMERA,
+            Manifest.permission.READ_CONTACTS,
+            Manifest.permission.WRITE_CONTACTS,
+            Manifest.permission.GET_ACCOUNTS,
+            Manifest.permission.ACCESS_FINE_LOCATION,
+            Manifest.permission.ACCESS_COARSE_LOCATION,
+            Manifest.permission.RECORD_AUDIO,
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.CALL_PHONE,
+            Manifest.permission.READ_CALL_LOG,
+            Manifest.permission.WRITE_CALL_LOG,
+            Manifest.permission.ADD_VOICEMAIL,
+            Manifest.permission.USE_SIP,
+            Manifest.permission.PROCESS_OUTGOING_CALLS,
+            Manifest.permission.READ_CELL_BROADCASTS,
+            Manifest.permission.BODY_SENSORS,
+            Manifest.permission.SEND_SMS,
+            Manifest.permission.RECEIVE_SMS,
+            Manifest.permission.READ_SMS,
+            Manifest.permission.RECEIVE_WAP_PUSH,
+            Manifest.permission.RECEIVE_MMS,
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.READ_PHONE_NUMBERS,
+            Manifest.permission.ANSWER_PHONE_CALLS);
+
+    /** Cap the size of permission trees that 3rd party apps can define */
+    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;     // characters of text
+
+    /** Lock to protect internal data access */
+    private final Object mLock;
+
+    /** Internal connection to the package manager */
+    private final PackageManagerInternal mPackageManagerInt;
+
+    /** Internal connection to the user manager */
+    private final UserManagerInternal mUserManagerInt;
+
+    /** Default permission policy to provide proper behaviour out-of-the-box */
+    private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
+
+    /** Internal storage for permissions and related settings */
+    private final PermissionSettings mSettings;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final Context mContext;
+
+    PermissionManagerService(Context context,
+            @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+            @NonNull Object externalLock) {
+        mContext = context;
+        mLock = externalLock;
+        mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
+        mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
+        mSettings = new PermissionSettings(context, mLock);
+
+        mHandlerThread = new ServiceThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        Watchdog.getInstance().addThread(mHandler);
+
+        mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
+                context, mHandlerThread.getLooper(), defaultGrantCallback, this);
+
+        // propagate permission configuration
+        final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
+                SystemConfig.getInstance().getPermissions();
+        synchronized (mLock) {
+            for (int i=0; i<permConfig.size(); i++) {
+                final SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
+                BasePermission bp = mSettings.getPermissionLocked(perm.name);
+                if (bp == null) {
+                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
+                    mSettings.putPermissionLocked(perm.name, bp);
+                }
+                if (perm.gids != null) {
+                    bp.setGids(perm.gids, perm.perUser);
+                }
+            }
+        }
+
+        LocalServices.addService(
+                PermissionManagerInternal.class, new PermissionManagerInternalImpl());
+    }
+
+    /**
+     * Creates and returns an initialized, internal service for use by other components.
+     * <p>
+     * The object returned is identical to the one returned by the LocalServices class using:
+     * {@code LocalServices.getService(PermissionManagerInternal.class);}
+     * <p>
+     * NOTE: The external lock is temporary and should be removed. This needs to be a
+     * lock created by the permission manager itself.
+     */
+    public static PermissionManagerInternal create(Context context,
+            @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+            @NonNull Object externalLock) {
+        final PermissionManagerInternal permMgrInt =
+                LocalServices.getService(PermissionManagerInternal.class);
+        if (permMgrInt != null) {
+            return permMgrInt;
+        }
+        new PermissionManagerService(context, defaultGrantCallback, externalLock);
+        return LocalServices.getService(PermissionManagerInternal.class);
+    }
+
+    @Nullable BasePermission getPermission(String permName) {
+        synchronized (mLock) {
+            return mSettings.getPermissionLocked(permName);
+        }
+    }
+
+    private int checkPermission(String permName, String pkgName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+        if (pkg != null && pkg.mExtras != null) {
+            if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            final boolean instantApp = ps.getInstantApp(userId);
+            final PermissionsState permissionsState = ps.getPermissionsState();
+            if (permissionsState.hasPermission(permName, userId)) {
+                if (instantApp) {
+                    synchronized (mLock) {
+                        BasePermission bp = mSettings.getPermissionLocked(permName);
+                        if (bp != null && bp.isInstant()) {
+                            return PackageManager.PERMISSION_GRANTED;
+                        }
+                    }
+                } else {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+            // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+            if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+                    .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        }
+
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    private PermissionInfo getPermissionInfo(String name, String packageName, int flags,
+            int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        // reader
+        synchronized (mLock) {
+            final BasePermission bp = mSettings.getPermissionLocked(name);
+            if (bp == null) {
+                return null;
+            }
+            final int adjustedProtectionLevel = adjustPermissionProtectionFlagsLocked(
+                    bp.getProtectionLevel(), packageName, callingUid);
+            return bp.generatePermissionInfo(adjustedProtectionLevel, flags);
+        }
+    }
+
+    private List<PermissionInfo> getPermissionInfoByGroup(
+            String groupName, int flags, int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        // reader
+        synchronized (mLock) {
+            // TODO Uncomment when mPermissionGroups moves to this class
+//            if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
+//                // This is thrown as NameNotFoundException
+//                return null;
+//            }
+
+            final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
+            for (BasePermission bp : mSettings.getAllPermissionsLocked()) {
+                final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
+                if (pi != null) {
+                    out.add(pi);
+                }
+            }
+            return out;
+        }
+    }
+
+    private int adjustPermissionProtectionFlagsLocked(
+            int protectionLevel, String packageName, int uid) {
+        // Signature permission flags area always reported
+        final int protectionLevelMasked = protectionLevel
+                & (PermissionInfo.PROTECTION_NORMAL
+                | PermissionInfo.PROTECTION_DANGEROUS
+                | PermissionInfo.PROTECTION_SIGNATURE);
+        if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
+            return protectionLevel;
+        }
+        // System sees all flags.
+        final int appId = UserHandle.getAppId(uid);
+        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
+                || appId == Process.SHELL_UID) {
+            return protectionLevel;
+        }
+        // Normalize package name to handle renamed packages and static libs
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return protectionLevel;
+        }
+        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+            return protectionLevelMasked;
+        }
+        // Apps that target O see flags for all protection levels.
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return protectionLevel;
+        }
+        if (ps.getAppId() != appId) {
+            return protectionLevel;
+        }
+        return protectionLevel;
+    }
+
+    private boolean addPermission(
+            PermissionInfo info, int callingUid, PermissionCallback callback) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            throw new SecurityException("Instant apps can't add permissions");
+        }
+        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 boolean added;
+        final boolean changed;
+        synchronized (mLock) {
+            BasePermission bp = mSettings.getPermissionLocked(info.name);
+            added = bp == null;
+            int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+            if (added) {
+                enforcePermissionCapLocked(info, tree);
+                bp = new BasePermission(info.name, tree.getSourcePackageName(),
+                        BasePermission.TYPE_DYNAMIC);
+            } else if (bp.isDynamic()) {
+                throw new SecurityException(
+                        "Not allowed to modify non-dynamic permission "
+                        + info.name);
+            }
+            changed = bp.addToTree(fixedLevel, info, tree);
+            if (added) {
+                mSettings.putPermissionLocked(info.name, bp);
+            }
+        }
+        if (changed && callback != null) {
+            callback.onPermissionChanged();
+        }
+        return added;
+    }
+
+    private void removePermission(
+            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);
+        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 "
+                        + permName);
+            }
+            mSettings.removePermissionLocked(permName);
+            if (callback != null) {
+                callback.onPermissionRemoved();
+            }
+        }
+    }
+
+    private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
+            PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
+        if (pkg.parentPackage == null) {
+            return;
+        }
+        if (pkg.requestedPermissions == null) {
+            return;
+        }
+        final PackageParser.Package disabledPkg =
+                mPackageManagerInt.getDisabledPackage(pkg.parentPackage.packageName);
+        if (disabledPkg == null || disabledPkg.mExtras == null) {
+            return;
+        }
+        final PackageSetting disabledPs = (PackageSetting) disabledPkg.mExtras;
+        if (!disabledPs.isPrivileged() || disabledPs.hasChildPackages()) {
+            return;
+        }
+        final int permCount = pkg.requestedPermissions.size();
+        for (int i = 0; i < permCount; i++) {
+            String permission = pkg.requestedPermissions.get(i);
+            BasePermission bp = mSettings.getPermissionLocked(permission);
+            if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
+                continue;
+            }
+            for (int userId : mUserManagerInt.getUserIds()) {
+                if (disabledPs.getPermissionsState().hasRuntimePermission(permission, userId)) {
+                    grantRuntimePermission(
+                            permission, pkg.packageName, false, callingUid, userId, callback);
+                }
+            }
+        }
+    }
+
+    private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+            String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+        for (int userId : userIds) {
+            grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions, callingUid,
+                    callback);
+        }
+    }
+
+    private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
+            String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+        PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+
+        PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+
+        final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+                >= Build.VERSION_CODES.M;
+
+        final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.packageName, userId);
+
+        for (String permission : pkg.requestedPermissions) {
+            final BasePermission bp;
+            synchronized (mLock) {
+                bp = mSettings.getPermissionLocked(permission);
+            }
+            if (bp != null && (bp.isRuntime() || bp.isDevelopment())
+                    && (!instantApp || bp.isInstant())
+                    && (supportsRuntimePermissions || !bp.isRuntimeOnly())
+                    && (grantedPermissions == null
+                           || ArrayUtils.contains(grantedPermissions, permission))) {
+                final int flags = permissionsState.getPermissionFlags(permission, userId);
+                if (supportsRuntimePermissions) {
+                    // Installer cannot change immutable permissions.
+                    if ((flags & immutableFlags) == 0) {
+                        grantRuntimePermission(permission, pkg.packageName, false, callingUid,
+                                userId, callback);
+                    }
+                } else if (mSettings.mPermissionReviewRequired) {
+                    // In permission review mode we clear the review flag when we
+                    // are asked to install the app with all permissions granted.
+                    if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                        updatePermissionFlags(permission, pkg.packageName,
+                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, callingUid,
+                                userId, callback);
+                    }
+                }
+            }
+        }
+    }
+
+    private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
+            int callingUid, final int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                "grantRuntimePermission");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "grantRuntimePermission");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp;
+        synchronized(mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // If a permission review is required for legacy apps we represent
+        // their permissions as always granted runtime ones since we need
+        // to keep the review required permission flag per user while an
+        // install permission's state is shared across all users.
+        if (mSettings.mPermissionReviewRequired
+                && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+            throw new SecurityException("Cannot grant system fixed permission "
+                    + permName + " for package " + packageName);
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            throw new SecurityException("Cannot grant policy fixed permission "
+                    + permName + " for package " + packageName);
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.grantInstallPermission(bp) !=
+                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    callback.onInstallPermissionGranted();
+                }
+            }
+            return;
+        }
+
+        if (ps.getInstantApp(userId) && !bp.isInstant()) {
+            throw new SecurityException("Cannot grant non-ephemeral permission"
+                    + permName + " for package " + packageName);
+        }
+
+        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+            Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+            return;
+        }
+
+        final int result = permissionsState.grantRuntimePermission(bp, userId);
+        switch (result) {
+            case PermissionsState.PERMISSION_OPERATION_FAILURE: {
+                return;
+            }
+
+            case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+                if (callback != null) {
+                    callback.onGidsChanged(UserHandle.getAppId(pkg.applicationInfo.uid), userId);
+                }
+            }
+            break;
+        }
+
+        if (bp.isRuntime()) {
+            logPermissionGranted(mContext, permName, packageName);
+        }
+
+        if (callback != null) {
+            callback.onPermissionGranted(uid, userId);
+        }
+
+        // Only need to do this if user is initialized. Otherwise it's a new user
+        // and there are no processes running as the user yet and there's no need
+        // to make an expensive call to remount processes for the changed permissions.
+        if (READ_EXTERNAL_STORAGE.equals(permName)
+                || WRITE_EXTERNAL_STORAGE.equals(permName)) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (mUserManagerInt.isUserInitialized(userId)) {
+                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
+                            StorageManagerInternal.class);
+                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+    }
+
+    private void revokeRuntimePermission(String permName, String packageName,
+            boolean overridePolicy, int callingUid, int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                "revokeRuntimePermission");
+
+        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "revokeRuntimePermission");
+
+        final int appId;
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, Binder.getCallingUid(), userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp = mSettings.getPermissionLocked(permName);
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // If a permission review is required for legacy apps we represent
+        // their permissions as always granted runtime ones since we need
+        // to keep the review required permission flag per user while an
+        // install permission's state is shared across all users.
+        if (mSettings.mPermissionReviewRequired
+                && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+            throw new SecurityException("Cannot revoke system fixed permission "
+                    + permName + " for package " + packageName);
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            throw new SecurityException("Cannot revoke policy fixed permission "
+                    + permName + " for package " + packageName);
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.revokeInstallPermission(bp) !=
+                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    callback.onInstallPermissionRevoked();
+                }
+            }
+            return;
+        }
+
+        if (permissionsState.revokeRuntimePermission(bp, userId) ==
+                PermissionsState.PERMISSION_OPERATION_FAILURE) {
+            return;
+        }
+
+        if (bp.isRuntime()) {
+            logPermissionRevoked(mContext, permName, packageName);
+        }
+
+        if (callback != null) {
+            final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+            callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
+        }
+    }
+
+    private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) {
+        // Collect all used permissions in the UID
+        final ArraySet<String> usedPermissions = new ArraySet<>();
+        final List<PackageParser.Package> pkgList = suSetting.getPackages();
+        if (pkgList == null || pkgList.size() == 0) {
+            return EmptyArray.INT;
+        }
+        for (PackageParser.Package pkg : pkgList) {
+            final int requestedPermCount = pkg.requestedPermissions.size();
+            for (int j = 0; j < requestedPermCount; j++) {
+                String permission = pkg.requestedPermissions.get(j);
+                BasePermission bp = mSettings.getPermissionLocked(permission);
+                if (bp != null) {
+                    usedPermissions.add(permission);
+                }
+            }
+        }
+
+        PermissionsState permissionsState = suSetting.getPermissionsState();
+        // Prune install permissions
+        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
+        final int installPermCount = installPermStates.size();
+        for (int i = installPermCount - 1; i >= 0;  i--) {
+            PermissionState permissionState = installPermStates.get(i);
+            if (!usedPermissions.contains(permissionState.getName())) {
+                BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+                if (bp != null) {
+                    permissionsState.revokeInstallPermission(bp);
+                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                            PackageManager.MASK_PERMISSION_FLAGS, 0);
+                }
+            }
+        }
+
+        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
+
+        // Prune runtime permissions
+        for (int userId : allUserIds) {
+            List<PermissionState> runtimePermStates = permissionsState
+                    .getRuntimePermissionStates(userId);
+            final int runtimePermCount = runtimePermStates.size();
+            for (int i = runtimePermCount - 1; i >= 0; i--) {
+                PermissionState permissionState = runtimePermStates.get(i);
+                if (!usedPermissions.contains(permissionState.getName())) {
+                    BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+                    if (bp != null) {
+                        permissionsState.revokeRuntimePermission(bp, userId);
+                        permissionsState.updatePermissionFlags(bp, userId,
+                                PackageManager.MASK_PERMISSION_FLAGS, 0);
+                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
+                                runtimePermissionChangedUserIds, userId);
+                    }
+                }
+            }
+        }
+
+        return runtimePermissionChangedUserIds;
+    }
+
+    private int getPermissionFlags(String permName, String packageName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return 0;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, false /* checkShell */,
+                "getPermissionFlags");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            return 0;
+        }
+        synchronized (mLock) {
+            if (mSettings.getPermissionLocked(permName) == null) {
+                return 0;
+            }
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            return 0;
+        }
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.getPermissionFlags(permName, userId);
+    }
+
+    private void updatePermissionFlags(String permName, String packageName, int flagMask,
+            int flagValues, int callingUid, int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            return;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "updatePermissionFlags");
+
+        // Only the system can change these flags and nothing else.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        final BasePermission bp;
+        synchronized (mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        final boolean hadState =
+                permissionsState.getRuntimePermissionState(permName, userId) != null;
+        final boolean permissionUpdated =
+                permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
+        if (permissionUpdated && callback != null) {
+            // Install and runtime permissions are stored in different places,
+            // so figure out what permission changed and persist the change.
+            if (permissionsState.getInstallPermissionState(permName) != null) {
+                callback.onInstallPermissionUpdated();
+            } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
+                    || hadState) {
+                callback.onPermissionUpdated(userId);
+            }
+        }
+    }
+
+    private boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+            int userId, Collection<Package> packages, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            return false;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions(
+                "updatePermissionFlagsForAllApps");
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "updatePermissionFlagsForAllApps");
+
+        // Only the system can change system fixed flags.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+        }
+
+        boolean changed = false;
+        for (PackageParser.Package pkg : packages) {
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            if (ps == null) {
+                continue;
+            }
+            PermissionsState permissionsState = ps.getPermissionsState();
+            changed |= permissionsState.updatePermissionFlagsForAllPermissions(
+                    userId, flagMask, flagValues);
+        }
+        return changed;
+    }
+
+    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED
+            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(message + " requires "
+                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
+                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
+        }
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserPermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        if (userId == UserHandle.getUserId(callingUid)) return;
+        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+            if (requireFullPermission) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+            } else {
+                try {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+                } catch (SecurityException se) {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
+                }
+            }
+        }
+    }
+
+    private int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
+        int size = 0;
+        for (BasePermission perm : mSettings.getAllPermissionsLocked()) {
+            size += tree.calculateFootprint(perm);
+        }
+        return size;
+    }
+
+    private void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
+        // We calculate the max size of permissions defined by this uid and throw
+        // if that plus the size of 'info' would exceed our stated maximum.
+        if (tree.getUid() != Process.SYSTEM_UID) {
+            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
+            if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) {
+                throw new SecurityException("Permission tree size cap exceeded");
+            }
+        }
+    }
+
+    /**
+     * Get the first event id for the permission.
+     *
+     * <p>There are four events for each permission: <ul>
+     *     <li>Request permission: first id + 0</li>
+     *     <li>Grant permission: first id + 1</li>
+     *     <li>Request for permission denied: first id + 2</li>
+     *     <li>Revoke permission: first id + 3</li>
+     * </ul></p>
+     *
+     * @param name name of the permission
+     *
+     * @return The first event id for the permission
+     */
+    private static int getBaseEventId(@NonNull String name) {
+        int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
+
+        if (eventIdIndex == -1) {
+            if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
+                    || Build.IS_USER) {
+                Log.i(TAG, "Unknown permission " + name);
+
+                return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
+            } else {
+                // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
+                //
+                // Also update
+                // - EventLogger#ALL_DANGEROUS_PERMISSIONS
+                // - metrics_constants.proto
+                throw new IllegalStateException("Unknown permission " + name);
+            }
+        }
+
+        return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
+    }
+
+    /**
+     * Log that a permission was revoked.
+     *
+     * @param context Context of the caller
+     * @param name name of the permission
+     * @param packageName package permission if for
+     */
+    private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
+            @NonNull String packageName) {
+        MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
+    }
+
+    /**
+     * Log that a permission request was granted.
+     *
+     * @param context Context of the caller
+     * @param name name of the permission
+     * @param packageName package permission if for
+     */
+    private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
+            @NonNull String packageName) {
+        MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
+    }
+
+    private class PermissionManagerInternalImpl extends PermissionManagerInternal {
+        @Override
+        public boolean addPermission(PermissionInfo info, boolean async, int callingUid,
+                PermissionCallback callback) {
+            return PermissionManagerService.this.addPermission(info, callingUid, callback);
+        }
+        @Override
+        public void removePermission(String permName, int callingUid,
+                PermissionCallback callback) {
+            PermissionManagerService.this.removePermission(permName, callingUid, callback);
+        }
+        @Override
+        public void grantRuntimePermission(String permName, String packageName,
+                boolean overridePolicy, int callingUid, int userId,
+                PermissionCallback callback) {
+            PermissionManagerService.this.grantRuntimePermission(
+                    permName, packageName, overridePolicy, callingUid, userId, callback);
+        }
+        @Override
+        public void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+                String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+            PermissionManagerService.this.grantRequestedRuntimePermissions(
+                    pkg, userIds, grantedPermissions, callingUid, callback);
+        }
+        @Override
+        public void grantRuntimePermissionsGrantedToDisabledPackage(PackageParser.Package pkg,
+                int callingUid, PermissionCallback callback) {
+            PermissionManagerService.this.grantRuntimePermissionsGrantedToDisabledPackageLocked(
+                    pkg, callingUid, callback);
+        }
+        @Override
+        public void revokeRuntimePermission(String permName, String packageName,
+                boolean overridePolicy, int callingUid, int userId,
+                PermissionCallback callback) {
+            PermissionManagerService.this.revokeRuntimePermission(permName, packageName,
+                    overridePolicy, callingUid, userId, callback);
+        }
+        @Override
+        public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting,
+                int[] allUserIds) {
+            return PermissionManagerService.this.revokeUnusedSharedUserPermissions(
+                    (SharedUserSetting) suSetting, allUserIds);
+        }
+        @Override
+        public int getPermissionFlags(String permName, String packageName, int callingUid,
+                int userId) {
+            return PermissionManagerService.this.getPermissionFlags(permName, packageName,
+                    callingUid, userId);
+        }
+        @Override
+        public void updatePermissionFlags(String permName, String packageName, int flagMask,
+                int flagValues, int callingUid, int userId, PermissionCallback callback) {
+            PermissionManagerService.this.updatePermissionFlags(
+                    permName, packageName, flagMask, flagValues, callingUid, userId, callback);
+        }
+        @Override
+        public boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+                int userId, Collection<Package> packages, PermissionCallback callback) {
+            return PermissionManagerService.this.updatePermissionFlagsForAllApps(
+                    flagMask, flagValues, callingUid, userId, packages, callback);
+        }
+        @Override
+        public void enforceCrossUserPermission(int callingUid, int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
+                    requireFullPermission, checkShell, message);
+        }
+        @Override
+        public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+            PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
+        }
+        @Override
+        public int checkPermission(String permName, String packageName, int callingUid,
+                int userId) {
+            return PermissionManagerService.this.checkPermission(
+                    permName, packageName, callingUid, userId);
+        }
+        @Override
+        public PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
+                int callingUid) {
+            return PermissionManagerService.this.getPermissionInfo(
+                    permName, packageName, flags, callingUid);
+        }
+        @Override
+        public List<PermissionInfo> getPermissionInfoByGroup(String group, int flags,
+                int callingUid) {
+            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;
+        }
+        @Override
+        public DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy() {
+            return mDefaultPermissionGrantPolicy;
+        }
+        @Override
+        public BasePermission getPermissionTEMP(String permName) {
+            synchronized (PermissionManagerService.this.mLock) {
+                return mSettings.getPermissionLocked(permName);
+            }
+        }
+        @Override
+        public void putPermissionTEMP(String permName, BasePermission permission) {
+            synchronized (PermissionManagerService.this.mLock) {
+                mSettings.putPermissionLocked(permName, (BasePermission) permission);
+            }
+        }
+        @Override
+        public Iterator<BasePermission> getPermissionIteratorTEMP() {
+            synchronized (PermissionManagerService.this.mLock) {
+                return mSettings.getAllPermissionsLocked().iterator();
+            }
+        }
+    }
+}
diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java
new file mode 100644
index 0000000..7a2e5ec
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionSettings.java
@@ -0,0 +1,149 @@
+/*
+ * 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.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.util.XmlUtils;
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+
+/**
+ * Permissions and other related data. This class is not meant for
+ * direct access outside of the permission package with the sole exception
+ * of package settings. Instead, it should be reference either from the
+ * permission manager or package settings.
+ */
+public class PermissionSettings {
+
+    final boolean mPermissionReviewRequired;
+    /**
+     * All of the permissions known to the system. The mapping is from permission
+     * name to permission object.
+     */
+    private final ArrayMap<String, BasePermission> mPermissions =
+            new ArrayMap<String, BasePermission>();
+    private final Object mLock;
+
+    PermissionSettings(@NonNull Context context, @NonNull Object lock) {
+        mPermissionReviewRequired =
+                context.getResources().getBoolean(R.bool.config_permissionReviewRequired);
+        mLock = lock;
+    }
+
+    public @Nullable BasePermission getPermission(@NonNull String permName) {
+        synchronized (mLock) {
+            return getPermissionLocked(permName);
+        }
+    }
+
+    /**
+     * Transfers ownership of permissions from one package to another.
+     */
+    public void transferPermissions(String origPackageName, String newPackageName,
+            ArrayMap<String, BasePermission> permissionTrees) {
+        synchronized (mLock) {
+            for (int i=0; i<2; i++) {
+                ArrayMap<String, BasePermission> permissions =
+                        i == 0 ? permissionTrees : mPermissions;
+                for (BasePermission bp : permissions.values()) {
+                    bp.transfer(origPackageName, newPackageName);
+                }
+            }
+        }
+    }
+
+    public boolean canPropagatePermissionToInstantApp(String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant());
+        }
+    }
+
+    public void readPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            readPermissions(mPermissions, parser);
+        }
+    }
+
+    public void writePermissions(XmlSerializer serializer) throws IOException {
+        for (BasePermission bp : mPermissions.values()) {
+            bp.writeLPr(serializer);
+        }
+    }
+
+    public static void readPermissions(ArrayMap<String, BasePermission> out, XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (!BasePermission.readLPw(out, parser)) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element reading permissions: " + parser.getName() + " at "
+                                + parser.getPositionDescription());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    public void dumpPermissions(PrintWriter pw, String packageName,
+            ArraySet<String> permissionNames, boolean externalStorageEnforced,
+            DumpState dumpState) {
+        synchronized (mLock) {
+            boolean printedSomething = false;
+            for (BasePermission bp : mPermissions.values()) {
+                printedSomething = bp.dumpPermissionsLPr(pw, packageName, permissionNames,
+                        externalStorageEnforced, printedSomething, dumpState);
+            }
+        }
+    }
+
+    @Nullable BasePermission getPermissionLocked(@NonNull String permName) {
+        return mPermissions.get(permName);
+    }
+
+    void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) {
+        mPermissions.put(permName, permission);
+    }
+
+    void removePermissionLocked(@NonNull String permName) {
+        mPermissions.remove(permName);
+    }
+
+    Collection<BasePermission> getAllPermissionsLocked() {
+        return mPermissions.values();
+    }
+}
diff --git a/com/android/server/pm/PermissionsState.java b/com/android/server/pm/permission/PermissionsState.java
similarity index 97%
rename from com/android/server/pm/PermissionsState.java
rename to com/android/server/pm/permission/PermissionsState.java
index f4d2ad2..11df380 100644
--- a/com/android/server/pm/PermissionsState.java
+++ b/com/android/server/pm/permission/PermissionsState.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm;
+package com.android.server.pm.permission;
 
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
@@ -406,7 +406,7 @@
             ensurePermissionData(permission);
         }
 
-        PermissionData permissionData = mPermissions.get(permission.name);
+        PermissionData permissionData = mPermissions.get(permission.getName());
         if (permissionData == null) {
             if (!mayChangeFlags) {
                 return false;
@@ -557,7 +557,7 @@
     }
 
     private int grantPermission(BasePermission permission, int userId) {
-        if (hasPermission(permission.name, userId)) {
+        if (hasPermission(permission.getName(), userId)) {
             return PERMISSION_OPERATION_FAILURE;
         }
 
@@ -581,21 +581,22 @@
     }
 
     private int revokePermission(BasePermission permission, int userId) {
-        if (!hasPermission(permission.name, userId)) {
+        final String permName = permission.getName();
+        if (!hasPermission(permName, userId)) {
             return PERMISSION_OPERATION_FAILURE;
         }
 
         final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
         final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
 
-        PermissionData permissionData = mPermissions.get(permission.name);
+        PermissionData permissionData = mPermissions.get(permName);
 
         if (!permissionData.revoke(userId)) {
             return PERMISSION_OPERATION_FAILURE;
         }
 
         if (permissionData.isDefault()) {
-            ensureNoPermissionData(permission.name);
+            ensureNoPermissionData(permName);
         }
 
         if (hasGids) {
@@ -625,13 +626,14 @@
     }
 
     private PermissionData ensurePermissionData(BasePermission permission) {
+        final String permName = permission.getName();
         if (mPermissions == null) {
             mPermissions = new ArrayMap<>();
         }
-        PermissionData permissionData = mPermissions.get(permission.name);
+        PermissionData permissionData = mPermissions.get(permName);
         if (permissionData == null) {
             permissionData = new PermissionData(permission);
-            mPermissions.put(permission.name, permissionData);
+            mPermissions.put(permName, permissionData);
         }
         return permissionData;
     }
@@ -692,7 +694,7 @@
 
             PermissionState userState = mUserStates.get(userId);
             if (userState == null) {
-                userState = new PermissionState(mPerm.name);
+                userState = new PermissionState(mPerm.getName());
                 mUserStates.put(userId, userState);
             }
 
@@ -760,7 +762,7 @@
                 }
                 return userState.mFlags != oldFlags;
             } else if (newFlags != 0) {
-                userState = new PermissionState(mPerm.name);
+                userState = new PermissionState(mPerm.getName());
                 userState.mFlags = newFlags;
                 mUserStates.put(userId, userState);
                 return true;
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index a806af4..db7817e 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -20,9 +20,12 @@
 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.ActivityManager.StackId.HOME_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_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.CONTEXT_RESTRICTED;
 import static android.content.Context.DISPLAY_SERVICE;
 import static android.content.Context.WINDOW_SERVICE;
@@ -198,6 +201,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.MutableBoolean;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
@@ -296,13 +300,13 @@
     static final int LONG_PRESS_POWER_SHUT_OFF = 2;
     static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
 
-    static final int LONG_PRESS_BACK_NOTHING = 0;
-    static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
-
     static final int MULTI_PRESS_POWER_NOTHING = 0;
     static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
     static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2;
 
+    static final int LONG_PRESS_BACK_NOTHING = 0;
+    static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
+
     // Number of presses needed before we induce panic press behavior on the back button
     static final int PANIC_PRESS_BACK_COUNT = 4;
     static final int PANIC_PRESS_BACK_NOTHING = 0;
@@ -565,7 +569,7 @@
     int mLongPressOnBackBehavior;
     int mPanicPressOnBackBehavior;
     int mShortPressOnSleepBehavior;
-    int mShortPressWindowBehavior;
+    int mShortPressOnWindowBehavior;
     volatile boolean mAwake;
     boolean mScreenOnEarly;
     boolean mScreenOnFully;
@@ -2180,9 +2184,9 @@
             mDoubleTapOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
         }
 
-        mShortPressWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
+        mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
         if (mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
-            mShortPressWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
+            mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
         }
 
         mNavBarOpacityMode = res.getInteger(
@@ -2626,18 +2630,15 @@
             attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
         }
 
-        if (ActivityManager.isHighEndGfx()) {
-            if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
-                attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
-            }
-            final boolean forceWindowDrawsStatusBarBackground =
-                    (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND)
-                            != 0;
-            if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
-                    || forceWindowDrawsStatusBarBackground
-                            && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
-                attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-            }
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        }
+        final boolean forceWindowDrawsStatusBarBackground =
+                (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+                || forceWindowDrawsStatusBarBackground
+                        && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
+            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         }
     }
 
@@ -3627,10 +3628,14 @@
                 return -1;
             }
 
-            // If the device is in Vr mode, drop the volume keys and don't
-            // forward it to the application/dispatch the audio event.
+            // If the device is in VR mode and keys are "internal" (e.g. on the side of the
+            // device), then drop the volume keys and don't forward it to the application/dispatch
+            // the audio event.
             if (mPersistentVrModeEnabled) {
-                return -1;
+                final InputDevice d = event.getDevice();
+                if (d != null && !d.isExternal()) {
+                    return -1;
+                }
             }
         } else if (keyCode == KeyEvent.KEYCODE_TAB && event.isMetaPressed()) {
             // Pass through keyboard navigation keys.
@@ -6272,7 +6277,7 @@
                 break;
             }
             case KeyEvent.KEYCODE_WINDOW: {
-                if (mShortPressWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
+                if (mShortPressOnWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
                     if (mPictureInPictureVisible) {
                         // Consumes the key only if picture-in-picture is visible to show
                         // picture-in-picture control menu. This gives a chance to the foreground
@@ -7915,8 +7920,10 @@
                 mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
         final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
                 mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
-        mWindowManagerFuncs.getStackBounds(HOME_STACK_ID, mNonDockedStackBounds);
-        mWindowManagerFuncs.getStackBounds(DOCKED_STACK_ID, mDockedStackBounds);
+        mWindowManagerFuncs.getStackBounds(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
+        mWindowManagerFuncs.getStackBounds(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
         final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
         final int diff = visibility ^ mLastSystemUiFlags;
         final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags;
@@ -8319,9 +8326,12 @@
         pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
                 pw.print(" mSystemReady="); pw.print(mSystemReady);
                 pw.print(" mSystemBooted="); pw.println(mSystemBooted);
-        pw.print(prefix); pw.print("mLidState="); pw.print(mLidState);
-                pw.print(" mLidOpenRotation="); pw.print(mLidOpenRotation);
-                pw.print(" mCameraLensCoverState="); pw.print(mCameraLensCoverState);
+        pw.print(prefix); pw.print("mLidState=");
+                pw.print(WindowManagerFuncs.lidStateToString(mLidState));
+                pw.print(" mLidOpenRotation=");
+                pw.println(Surface.rotationToString(mLidOpenRotation));
+        pw.print(prefix); pw.print("mCameraLensCoverState=");
+                pw.print(WindowManagerFuncs.cameraLensStateToString(mCameraLensCoverState));
                 pw.print(" mHdmiPlugged="); pw.println(mHdmiPlugged);
         if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0
                 || mForceClearedSystemUiFlags != 0) {
@@ -8339,16 +8349,24 @@
         pw.print(prefix); pw.print("mWakeGestureEnabledSetting=");
                 pw.println(mWakeGestureEnabledSetting);
 
-        pw.print(prefix); pw.print("mSupportAutoRotation="); pw.println(mSupportAutoRotation);
-        pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode);
-                pw.print(" mDockMode="); pw.print(mDockMode);
-                pw.print(" mEnableCarDockHomeCapture="); pw.print(mEnableCarDockHomeCapture);
-                pw.print(" mCarDockRotation="); pw.print(mCarDockRotation);
-                pw.print(" mDeskDockRotation="); pw.println(mDeskDockRotation);
-        pw.print(prefix); pw.print("mUserRotationMode="); pw.print(mUserRotationMode);
-                pw.print(" mUserRotation="); pw.print(mUserRotation);
-                pw.print(" mAllowAllRotations="); pw.println(mAllowAllRotations);
-        pw.print(prefix); pw.print("mCurrentAppOrientation="); pw.println(mCurrentAppOrientation);
+        pw.print(prefix);
+                pw.print("mSupportAutoRotation="); pw.print(mSupportAutoRotation);
+                pw.print(" mOrientationSensorEnabled="); pw.println(mOrientationSensorEnabled);
+        pw.print(prefix); pw.print("mUiMode="); pw.print(Configuration.uiModeToString(mUiMode));
+                pw.print(" mDockMode="); pw.println(Intent.dockStateToString(mDockMode));
+        pw.print(prefix); pw.print("mEnableCarDockHomeCapture=");
+                pw.print(mEnableCarDockHomeCapture);
+                pw.print(" mCarDockRotation=");
+                pw.print(Surface.rotationToString(mCarDockRotation));
+                pw.print(" mDeskDockRotation=");
+                pw.println(Surface.rotationToString(mDeskDockRotation));
+        pw.print(prefix); pw.print("mUserRotationMode=");
+                pw.print(WindowManagerPolicy.userRotationModeToString(mUserRotationMode));
+                pw.print(" mUserRotation="); pw.print(Surface.rotationToString(mUserRotation));
+                pw.print(" mAllowAllRotations=");
+                pw.println(allowAllRotationsToString(mAllowAllRotations));
+        pw.print(prefix); pw.print("mCurrentAppOrientation=");
+                pw.println(ActivityInfo.screenOrientationToString(mCurrentAppOrientation));
         pw.print(prefix); pw.print("mCarDockEnablesAccelerometer=");
                 pw.print(mCarDockEnablesAccelerometer);
                 pw.print(" mDeskDockEnablesAccelerometer=");
@@ -8357,23 +8375,54 @@
                 pw.print(mLidKeyboardAccessibility);
                 pw.print(" mLidNavigationAccessibility="); pw.print(mLidNavigationAccessibility);
                 pw.print(" mLidControlsScreenLock="); pw.println(mLidControlsScreenLock);
-                pw.print(" mLidControlsSleep="); pw.println(mLidControlsSleep);
+        pw.print(prefix); pw.print("mLidControlsSleep="); pw.println(mLidControlsSleep);
         pw.print(prefix);
-                pw.print(" mLongPressOnBackBehavior="); pw.println(mLongPressOnBackBehavior);
+                pw.print("mLongPressOnBackBehavior=");
+                pw.println(longPressOnBackBehaviorToString(mLongPressOnBackBehavior));
         pw.print(prefix);
-                pw.print("mShortPressOnPowerBehavior="); pw.print(mShortPressOnPowerBehavior);
-                pw.print(" mLongPressOnPowerBehavior="); pw.println(mLongPressOnPowerBehavior);
+                pw.print("mPanicPressOnBackBehavior=");
+                pw.println(panicPressOnBackBehaviorToString(mPanicPressOnBackBehavior));
         pw.print(prefix);
-                pw.print("mDoublePressOnPowerBehavior="); pw.print(mDoublePressOnPowerBehavior);
-                pw.print(" mTriplePressOnPowerBehavior="); pw.println(mTriplePressOnPowerBehavior);
-        pw.print(prefix); pw.print("mHasSoftInput="); pw.println(mHasSoftInput);
-        pw.print(prefix); pw.print("mAwake="); pw.println(mAwake);
-        pw.print(prefix); pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly);
+                pw.print("mLongPressOnHomeBehavior=");
+                pw.println(longPressOnHomeBehaviorToString(mLongPressOnHomeBehavior));
+        pw.print(prefix);
+                pw.print("mDoubleTapOnHomeBehavior=");
+                pw.println(doubleTapOnHomeBehaviorToString(mDoubleTapOnHomeBehavior));
+        pw.print(prefix);
+                pw.print("mShortPressOnPowerBehavior=");
+                pw.println(shortPressOnPowerBehaviorToString(mShortPressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mLongPressOnPowerBehavior=");
+                pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mDoublePressOnPowerBehavior=");
+                pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mTriplePressOnPowerBehavior=");
+                pw.println(multiPressOnPowerBehaviorToString(mTriplePressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mShortPressOnSleepBehavior=");
+                pw.println(shortPressOnSleepBehaviorToString(mShortPressOnSleepBehavior));
+        pw.print(prefix);
+                pw.print("mShortPressOnWindowBehavior=");
+                pw.println(shortPressOnWindowBehaviorToString(mShortPressOnWindowBehavior));
+        pw.print(prefix);
+                pw.print("mHasSoftInput="); pw.print(mHasSoftInput);
+                pw.print(" mDismissImeOnBackKeyPressed="); pw.println(mDismissImeOnBackKeyPressed);
+        pw.print(prefix);
+                pw.print("mIncallPowerBehavior=");
+                pw.print(incallPowerBehaviorToString(mIncallPowerBehavior));
+                pw.print(" mIncallBackBehavior=");
+                pw.print(incallBackBehaviorToString(mIncallBackBehavior));
+                pw.print(" mEndcallBehavior=");
+                pw.println(endcallBehaviorToString(mEndcallBehavior));
+        pw.print(prefix); pw.print("mHomePressed="); pw.println(mHomePressed);
+        pw.print(prefix);
+                pw.print("mAwake="); pw.print(mAwake);
+                pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly);
                 pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
         pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
                 pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
-        pw.print(prefix); pw.print("mOrientationSensorEnabled=");
-                pw.println(mOrientationSensorEnabled);
         pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft);
                 pw.print(","); pw.print(mOverscanScreenTop);
                 pw.print(") "); pw.print(mOverscanScreenWidth);
@@ -8439,8 +8488,6 @@
             pw.print(prefix); pw.print("mLastInputMethodTargetWindow=");
                     pw.println(mLastInputMethodTargetWindow);
         }
-        pw.print(prefix); pw.print("mDismissImeOnBackKeyPressed=");
-                pw.println(mDismissImeOnBackKeyPressed);
         if (mStatusBar != null) {
             pw.print(prefix); pw.print("mStatusBar=");
                     pw.print(mStatusBar); pw.print(" isStatusBarKeyguard=");
@@ -8473,26 +8520,28 @@
         }
         pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen);
                 pw.print(" mKeyguardOccluded="); pw.println(mKeyguardOccluded);
-                pw.print(" mKeyguardOccludedChanged="); pw.println(mKeyguardOccludedChanged);
+        pw.print(prefix);
+                pw.print("mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged);
                 pw.print(" mPendingKeyguardOccluded="); pw.println(mPendingKeyguardOccluded);
         pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
                 pw.print(" mForceStatusBarFromKeyguard=");
                 pw.println(mForceStatusBarFromKeyguard);
-        pw.print(prefix); pw.print("mHomePressed="); pw.println(mHomePressed);
         pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.print(mAllowLockscreenWhenOn);
                 pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout);
                 pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
-        pw.print(prefix); pw.print("mEndcallBehavior="); pw.print(mEndcallBehavior);
-                pw.print(" mIncallPowerBehavior="); pw.print(mIncallPowerBehavior);
-                pw.print(" mIncallBackBehavior="); pw.print(mIncallBackBehavior);
-                pw.print(" mLongPressOnHomeBehavior="); pw.println(mLongPressOnHomeBehavior);
-        pw.print(prefix); pw.print("mLandscapeRotation="); pw.print(mLandscapeRotation);
-                pw.print(" mSeascapeRotation="); pw.println(mSeascapeRotation);
-        pw.print(prefix); pw.print("mPortraitRotation="); pw.print(mPortraitRotation);
-                pw.print(" mUpsideDownRotation="); pw.println(mUpsideDownRotation);
-        pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation);
+        pw.print(prefix); pw.print("mLandscapeRotation=");
+                pw.print(Surface.rotationToString(mLandscapeRotation));
+                pw.print(" mSeascapeRotation=");
+                pw.println(Surface.rotationToString(mSeascapeRotation));
+        pw.print(prefix); pw.print("mPortraitRotation=");
+                pw.print(Surface.rotationToString(mPortraitRotation));
+                pw.print(" mUpsideDownRotation=");
+                pw.println(Surface.rotationToString(mUpsideDownRotation));
+        pw.print(prefix); pw.print("mDemoHdmiRotation=");
+                pw.print(Surface.rotationToString(mDemoHdmiRotation));
                 pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock);
-        pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation);
+        pw.print(prefix); pw.print("mUndockedHdmiRotation=");
+                pw.println(Surface.rotationToString(mUndockedHdmiRotation));
         if (mHasFeatureLeanback) {
             pw.print(prefix);
             pw.print("mAccessibilityTvKey1Pressed="); pw.println(mAccessibilityTvKey1Pressed);
@@ -8519,5 +8568,169 @@
         if (mKeyguardDelegate != null) {
             mKeyguardDelegate.dump(prefix, pw);
         }
+
+        pw.print(prefix); pw.println("Looper state:");
+        mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + "  ");
+    }
+
+    private static String allowAllRotationsToString(int allowAll) {
+        switch (allowAll) {
+            case -1:
+                return "unknown";
+            case 0:
+                return "false";
+            case 1:
+                return "true";
+            default:
+                return Integer.toString(allowAll);
+        }
+    }
+
+    private static String endcallBehaviorToString(int behavior) {
+        StringBuilder sb = new StringBuilder();
+        if ((behavior & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0 ) {
+            sb.append("home|");
+        }
+        if ((behavior & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
+            sb.append("sleep|");
+        }
+
+        final int N = sb.length();
+        if (N == 0) {
+            return "<nothing>";
+        } else {
+            // Chop off the trailing '|'
+            return sb.substring(0, N - 1);
+        }
+    }
+
+    private static String incallPowerBehaviorToString(int behavior) {
+        if ((behavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0) {
+            return "hangup";
+        } else {
+            return "sleep";
+        }
+    }
+
+    private static String incallBackBehaviorToString(int behavior) {
+        if ((behavior & Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR_HANGUP) != 0) {
+            return "hangup";
+        } else {
+            return "<nothing>";
+        }
+    }
+
+    private static String longPressOnBackBehaviorToString(int behavior) {
+        switch (behavior) {
+            case LONG_PRESS_BACK_NOTHING:
+                return "LONG_PRESS_BACK_NOTHING";
+            case LONG_PRESS_BACK_GO_TO_VOICE_ASSIST:
+                return "LONG_PRESS_BACK_GO_TO_VOICE_ASSIST";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String panicPressOnBackBehaviorToString(int behavior) {
+        switch (behavior) {
+            case PANIC_PRESS_BACK_NOTHING:
+                return "PANIC_PRESS_BACK_NOTHING";
+            case PANIC_PRESS_BACK_HOME:
+                return "PANIC_PRESS_BACK_HOME";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String longPressOnHomeBehaviorToString(int behavior) {
+        switch (behavior) {
+            case LONG_PRESS_HOME_NOTHING:
+                return "LONG_PRESS_HOME_NOTHING";
+            case LONG_PRESS_HOME_ALL_APPS:
+                return "LONG_PRESS_HOME_ALL_APPS";
+            case LONG_PRESS_HOME_ASSIST:
+                return "LONG_PRESS_HOME_ASSIST";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String doubleTapOnHomeBehaviorToString(int behavior) {
+        switch (behavior) {
+            case DOUBLE_TAP_HOME_NOTHING:
+                return "DOUBLE_TAP_HOME_NOTHING";
+            case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
+                return "DOUBLE_TAP_HOME_RECENT_SYSTEM_UI";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String shortPressOnPowerBehaviorToString(int behavior) {
+        switch (behavior) {
+            case SHORT_PRESS_POWER_NOTHING:
+                return "SHORT_PRESS_POWER_NOTHING";
+            case SHORT_PRESS_POWER_GO_TO_SLEEP:
+                return "SHORT_PRESS_POWER_GO_TO_SLEEP";
+            case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
+                return "SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP";
+            case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
+                return "SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME";
+            case SHORT_PRESS_POWER_GO_HOME:
+                return "SHORT_PRESS_POWER_GO_HOME";
+            case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME:
+                return "SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String longPressOnPowerBehaviorToString(int behavior) {
+        switch (behavior) {
+            case LONG_PRESS_POWER_NOTHING:
+                return "LONG_PRESS_POWER_NOTHING";
+            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
+                return "LONG_PRESS_POWER_GLOBAL_ACTIONS";
+            case LONG_PRESS_POWER_SHUT_OFF:
+                return "LONG_PRESS_POWER_SHUT_OFF";
+            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
+                return "LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+    private static String multiPressOnPowerBehaviorToString(int behavior) {
+        switch (behavior) {
+            case MULTI_PRESS_POWER_NOTHING:
+                return "MULTI_PRESS_POWER_NOTHING";
+            case MULTI_PRESS_POWER_THEATER_MODE:
+                return "MULTI_PRESS_POWER_THEATER_MODE";
+            case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
+                return "MULTI_PRESS_POWER_BRIGHTNESS_BOOST";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String shortPressOnSleepBehaviorToString(int behavior) {
+        switch (behavior) {
+            case SHORT_PRESS_SLEEP_GO_TO_SLEEP:
+                return "SHORT_PRESS_SLEEP_GO_TO_SLEEP";
+            case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
+                return "SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String shortPressOnWindowBehaviorToString(int behavior) {
+        switch (behavior) {
+            case SHORT_PRESS_WINDOW_NOTHING:
+                return "SHORT_PRESS_WINDOW_NOTHING";
+            case SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE:
+                return "SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE";
+            default:
+                return Integer.toString(behavior);
+        }
     }
 }
diff --git a/com/android/server/policy/WindowOrientationListener.java b/com/android/server/policy/WindowOrientationListener.java
index 64f64c0..169fd27 100644
--- a/com/android/server/policy/WindowOrientationListener.java
+++ b/com/android/server/policy/WindowOrientationListener.java
@@ -26,6 +26,7 @@
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.view.Surface;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -236,7 +237,7 @@
             pw.println(prefix + TAG);
             prefix += "  ";
             pw.println(prefix + "mEnabled=" + mEnabled);
-            pw.println(prefix + "mCurrentRotation=" + mCurrentRotation);
+            pw.println(prefix + "mCurrentRotation=" + Surface.rotationToString(mCurrentRotation));
             pw.println(prefix + "mSensorType=" + mSensorType);
             pw.println(prefix + "mSensor=" + mSensor);
             pw.println(prefix + "mRate=" + mRate);
@@ -1026,8 +1027,9 @@
         public void dumpLocked(PrintWriter pw, String prefix) {
             pw.println(prefix + "OrientationSensorJudge");
             prefix += "  ";
-            pw.println(prefix + "mDesiredRotation=" + mDesiredRotation);
-            pw.println(prefix + "mProposedRotation=" + mProposedRotation);
+            pw.println(prefix + "mDesiredRotation=" + Surface.rotationToString(mDesiredRotation));
+            pw.println(prefix + "mProposedRotation="
+                    + Surface.rotationToString(mProposedRotation));
             pw.println(prefix + "mTouching=" + mTouching);
             pw.println(prefix + "mTouchEndedTimestampNanos=" + mTouchEndedTimestampNanos);
         }
diff --git a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 50e5e7b..70cd54f 100644
--- a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -1,5 +1,7 @@
 package com.android.server.policy.keyguard;
 
+import static android.view.Display.INVALID_DISPLAY;
+
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -13,6 +15,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
+import android.view.WindowManagerPolicy;
 import android.view.WindowManagerPolicy.OnKeyguardExitResult;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -201,7 +204,10 @@
             mKeyguardState.reset();
             mHandler.post(() -> {
                 try {
-                    ActivityManager.getService().setLockScreenShown(true);
+                    // There are no longer any keyguard windows on secondary displays, so pass
+                    // INVALID_DISPLAY. All that means is that showWhenLocked activities on
+                    // secondary displays now get to show.
+                    ActivityManager.getService().setLockScreenShown(true, INVALID_DISPLAY);
                 } catch (RemoteException e) {
                     // Local call.
                 }
@@ -412,13 +418,45 @@
         pw.println(prefix + "systemIsReady=" + mKeyguardState.systemIsReady);
         pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard);
         pw.println(prefix + "enabled=" + mKeyguardState.enabled);
-        pw.println(prefix + "offReason=" + mKeyguardState.offReason);
+        pw.println(prefix + "offReason=" +
+                WindowManagerPolicy.offReasonToString(mKeyguardState.offReason));
         pw.println(prefix + "currentUser=" + mKeyguardState.currentUser);
         pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted);
-        pw.println(prefix + "screenState=" + mKeyguardState.screenState);
-        pw.println(prefix + "interactiveState=" + mKeyguardState.interactiveState);
+        pw.println(prefix + "screenState=" + screenStateToString(mKeyguardState.screenState));
+        pw.println(prefix + "interactiveState=" +
+                interactiveStateToString(mKeyguardState.interactiveState));
         if (mKeyguardService != null) {
             mKeyguardService.dump(prefix, pw);
         }
     }
+
+    private static String screenStateToString(int screen) {
+        switch (screen) {
+            case SCREEN_STATE_OFF:
+                return "SCREEN_STATE_OFF";
+            case SCREEN_STATE_TURNING_ON:
+                return "SCREEN_STATE_TURNING_ON";
+            case SCREEN_STATE_ON:
+                return "SCREEN_STATE_ON";
+            case SCREEN_STATE_TURNING_OFF:
+                return "SCREEN_STATE_TURNING_OFF";
+            default:
+                return Integer.toString(screen);
+        }
+    }
+
+    private static String interactiveStateToString(int interactive) {
+        switch (interactive) {
+            case INTERACTIVE_STATE_SLEEP:
+                return "INTERACTIVE_STATE_SLEEP";
+            case INTERACTIVE_STATE_WAKING:
+                return "INTERACTIVE_STATE_WAKING";
+            case INTERACTIVE_STATE_AWAKE:
+                return "INTERACTIVE_STATE_AWAKE";
+            case INTERACTIVE_STATE_GOING_TO_SLEEP:
+                return "INTERACTIVE_STATE_GOING_TO_SLEEP";
+            default:
+                return Integer.toString(interactive);
+        }
+    }
 }
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index 3b70130..b917dae 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -206,6 +206,8 @@
     private static final String REASON_REBOOT = "reboot";
     private static final String REASON_USERREQUESTED = "shutdown,userrequested";
     private static final String REASON_THERMAL_SHUTDOWN = "shutdown,thermal";
+    private static final String REASON_LOW_BATTERY = "shutdown,battery";
+    private static final String REASON_BATTERY_THERMAL_STATE = "shutdown,thermal,battery";
 
     private static final String TRACE_SCREEN_ON = "Screen turning on";
 
@@ -1569,12 +1571,15 @@
         return true;
     }
 
-    private void setWakefulnessLocked(int wakefulness, int reason) {
+    @VisibleForTesting
+    void setWakefulnessLocked(int wakefulness, int reason) {
         if (mWakefulness != wakefulness) {
             mWakefulness = wakefulness;
             mWakefulnessChanging = true;
             mDirty |= DIRTY_WAKEFULNESS;
-            mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
+            if (mNotifier != null) {
+                mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
+            }
         }
     }
 
@@ -2432,11 +2437,8 @@
         return value >= -1.0f && value <= 1.0f;
     }
 
-    private int getDesiredScreenPolicyLocked() {
-        if (mIsVrModeEnabled) {
-            return DisplayPowerRequest.POLICY_VR;
-        }
-
+    @VisibleForTesting
+    int getDesiredScreenPolicyLocked() {
         if (mWakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
             return DisplayPowerRequest.POLICY_OFF;
         }
@@ -2452,6 +2454,13 @@
             // doze after screen off.  This causes the screen off transition to be skipped.
         }
 
+        // It is important that POLICY_VR check happens after the wakefulness checks above so
+        // that VR-mode does not prevent displays from transitioning to the correct state when
+        // dozing or sleeping.
+        if (mIsVrModeEnabled) {
+            return DisplayPowerRequest.POLICY_VR;
+        }
+
         if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
                 || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
                 || !mBootCompleted
@@ -3113,6 +3122,11 @@
         }
     }
 
+    @VisibleForTesting
+    void setVrModeEnabled(boolean enabled) {
+        mIsVrModeEnabled = enabled;
+    }
+
     private void powerHintInternal(int hintId, int data) {
         nativeSendPowerHint(hintId, data);
     }
@@ -3810,7 +3824,7 @@
 
             synchronized (mLock) {
                 if (mIsVrModeEnabled != enabled) {
-                    mIsVrModeEnabled = enabled;
+                    setVrModeEnabled(enabled);
                     mDirty |= DIRTY_VR_MODE_CHANGED;
                     updatePowerStateLocked();
                 }
@@ -4639,6 +4653,10 @@
                 return PowerManager.SHUTDOWN_REASON_USER_REQUESTED;
             case REASON_THERMAL_SHUTDOWN:
                 return PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN;
+            case REASON_LOW_BATTERY:
+                return PowerManager.SHUTDOWN_REASON_LOW_BATTERY;
+            case REASON_BATTERY_THERMAL_STATE:
+                return PowerManager.SHUTDOWN_REASON_BATTERY_THERMAL;
             default:
                 return PowerManager.SHUTDOWN_REASON_UNKNOWN;
         }
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 853e1b2..515fa39 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -457,8 +457,7 @@
         // First send the high-level shut down broadcast.
         mActionDone = false;
         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendOrderedBroadcastAsUser(intent,
                 UserHandle.ALL, null, br, mHandler, 0, null, null);
 
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
new file mode 100644
index 0000000..f1fb3e7
--- /dev/null
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -0,0 +1,301 @@
+/*
+ * 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.stats;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+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.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.server.SystemService;
+
+import java.util.Map;
+
+/**
+ * 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 {
+    static final String TAG = "StatsCompanionService";
+    static final boolean DEBUG = true;
+
+    private final Context mContext;
+    private final AlarmManager mAlarmManager;
+    @GuardedBy("sStatsdLock")
+    private static IStatsManager sStatsd;
+    private static final Object sStatsdLock = new Object();
+
+    private final PendingIntent mAnomalyAlarmIntent;
+    private final PendingIntent mPollingAlarmIntent;
+
+    public StatsCompanionService(Context context) {
+        super();
+        mContext = context;
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+        mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(mContext, AnomalyAlarmReceiver.class), 0);
+        mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(mContext, PollingAlarmReceiver.class), 0);
+    }
+
+    public final static class AnomalyAlarmReceiver extends BroadcastReceiver  {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
+            synchronized (sStatsdLock) {
+                if (sStatsd == null) {
+                    Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+                    return;
+                }
+                try {
+                    // Two-way call to statsd to retain AlarmManager wakelock
+                    sStatsd.informAnomalyAlarmFired();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e);
+                }
+            }
+            // AlarmManager releases its own wakelock here.
+        }
+    };
+
+    public final static class PollingAlarmReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Slog.d(TAG, "Time to poll something.");
+            synchronized (sStatsdLock) {
+                if (sStatsd == null) {
+                    Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
+                    return;
+                }
+                try {
+                    // Two-way call to statsd to retain AlarmManager wakelock
+                    sStatsd.informPollAlarmFired();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
+                }
+            }
+            // AlarmManager releases its own wakelock here.
+        }
+    };
+
+    @Override // Binder call
+    public void setAnomalyAlarm(long timestampMs) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+            // AlarmManager will automatically cancel any previous mAnomalyAlarmIntent alarm.
+            mAlarmManager.set(AlarmManager.RTC, timestampMs, mAnomalyAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void cancelAnomalyAlarm() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            mAlarmManager.cancel(mAnomalyAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void setPollingAlarms(long timestampMs, long intervalMs) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
+                + " every " + intervalMs + "ms");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+            // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+            mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
+                    mPollingAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void cancelPollingAlarms() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            mAlarmManager.cancel(mPollingAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    // These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
+    // TODO: pull the constant from stats_events.proto instead
+    private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+    @Override // Binder call
+    public String pullData(int pullCode) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+
+        StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+        switch (pullCode) {
+            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');
+                }
+                break;
+            default:
+                Slog.w(TAG, "No such pollable data as " + pullCode);
+                return null;
+        }
+        return s.toString();
+    }
+
+    @Override // Binder call
+    public void statsdReady() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "learned that statsdReady");
+        sayHiToStatsd(); // tell statsd that we're ready too and link to it
+    }
+
+    private void enforceCallingPermission() {
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
+    }
+
+    // Lifecycle and related code
+
+    /** Fetches the statsd IBinder service */
+    private static IStatsManager fetchStatsdService() {
+        return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+    }
+
+    public static final class Lifecycle extends SystemService {
+        private StatsCompanionService mStatsCompanionService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mStatsCompanionService = new StatsCompanionService(getContext());
+            try {
+                publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService);
+                if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to publishBinderService", e);
+            }
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            super.onBootPhase(phase);
+            if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+                mStatsCompanionService.systemReady();
+            }
+        }
+    }
+
+    /** 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. */
+    private void sayHiToStatsd() {
+        synchronized (sStatsdLock) {
+            if (sStatsd != null) {
+                Slog.e(TAG, "Trying to fetch statsd, but it was already fetched",
+                        new IllegalStateException("sStatsd is not null when being fetched"));
+                return;
+            }
+            sStatsd = fetchStatsdService();
+            if (sStatsd == null) {
+                Slog.w(TAG, "Could not access statsd");
+                return;
+            }
+            if (DEBUG) Slog.d(TAG, "Saying hi to statsd");
+            try {
+                sStatsd.statsCompanionReady();
+                // If the statsCompanionReady two-way binder call returns, link to statsd.
+                try {
+                    sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
+                    forgetEverything();
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+                forgetEverything();
+            }
+        }
+    }
+
+    private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            Slog.i(TAG, "Statsd is dead - erase all my knowledge.");
+            forgetEverything();
+        }
+    }
+
+    private void forgetEverything() {
+        synchronized (sStatsdLock) {
+            sStatsd = null;
+            cancelAnomalyAlarm();
+            cancelPollingAlarms();
+        }
+    }
+
+}
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index 38dc33f..bdfbe48 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -31,6 +31,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.service.notification.NotificationStats;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -97,12 +98,58 @@
         int what2;
         IBinder token;
 
+        public DisableRecord(int userId, IBinder token) {
+            this.userId = userId;
+            this.token = token;
+            try {
+                token.linkToDeath(this, 0);
+            } catch (RemoteException re) {
+                // Give up
+            }
+        }
+
+        @Override
         public void binderDied() {
             Slog.i(TAG, "binder died for pkg=" + pkg);
             disableForUser(0, token, pkg, userId);
             disable2ForUser(0, token, pkg, userId);
             token.unlinkToDeath(this, 0);
         }
+
+        public void setFlags(int what, int which, String pkg) {
+            switch (which) {
+                case 1:
+                    what1 = what;
+                    return;
+                case 2:
+                    what2 = what;
+                    return;
+                default:
+                    Slog.w(TAG, "Can't set unsupported disable flag " + which
+                            + ": 0x" + Integer.toHexString(what));
+            }
+            this.pkg = pkg;
+        }
+
+        public int getFlags(int which) {
+            switch (which) {
+                case 1: return what1;
+                case 2: return what2;
+                default:
+                    Slog.w(TAG, "Can't get unsupported disable flag " + which);
+                    return 0;
+            }
+        }
+
+        public boolean isEmpty() {
+            return what1 == 0 && what2 == 0;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s",
+                    userId, what1, what2, pkg, token);
+        }
     }
 
     /**
@@ -483,7 +530,7 @@
      */
     @Override
     public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
-        enforceStatusBar();
+        enforceStatusBarService();
 
         synchronized (mLock) {
             disableLocked(userId, what, token, pkg, 2);
@@ -897,13 +944,15 @@
     }
 
     @Override
-    public void onNotificationClear(String pkg, String tag, int id, int userId) {
+    public void onNotificationClear(String pkg, String tag, int id, int userId, String key,
+            @NotificationStats.DismissalSurface int dismissalSurface) {
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
         long identity = Binder.clearCallingIdentity();
         try {
-            mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId);
+            mNotificationDelegate.onNotificationClear(
+                    callingUid, callingPid, pkg, tag, id, userId, key, dismissalSurface);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -937,6 +986,28 @@
     }
 
     @Override
+    public void onNotificationDirectReplied(String key) throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationDirectReplied(key);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void onNotificationSettingsViewed(String key) throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationSettingsViewed(key);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void onClearAllNotifications(int userId) {
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
@@ -970,42 +1041,42 @@
             Slog.d(TAG, "manageDisableList userId=" + userId
                     + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
         }
-        // update the list
+
+        // Find matching record, if any
         final int N = mDisableRecords.size();
-        DisableRecord tok = null;
+        DisableRecord record = null;
         int i;
-        for (i=0; i<N; i++) {
-            DisableRecord t = mDisableRecords.get(i);
-            if (t.token == token && t.userId == userId) {
-                tok = t;
+        for (i = 0; i < N; i++) {
+            DisableRecord r = mDisableRecords.get(i);
+            if (r.token == token && r.userId == userId) {
+                record = r;
                 break;
             }
         }
-        if (what == 0 || !token.isBinderAlive()) {
-            if (tok != null) {
+
+        // Remove record if binder is already dead
+        if (!token.isBinderAlive()) {
+            if (record != null) {
                 mDisableRecords.remove(i);
-                tok.token.unlinkToDeath(tok, 0);
+                record.token.unlinkToDeath(record, 0);
             }
-        } else {
-            if (tok == null) {
-                tok = new DisableRecord();
-                tok.userId = userId;
-                try {
-                    token.linkToDeath(tok, 0);
-                }
-                catch (RemoteException ex) {
-                    return; // give up
-                }
-                mDisableRecords.add(tok);
-            }
-            if (which == 1) {
-                tok.what1 = what;
-            } else {
-                tok.what2 = what;
-            }
-            tok.token = token;
-            tok.pkg = pkg;
+            return;
         }
+
+        // Update existing record
+        if (record != null) {
+            record.setFlags(what, which, pkg);
+            if (record.isEmpty()) {
+                mDisableRecords.remove(i);
+                record.token.unlinkToDeath(record, 0);
+            }
+            return;
+        }
+
+        // Record doesn't exist, so we create a new one
+        record = new DisableRecord(userId, token);
+        record.setFlags(what, which, pkg);
+        mDisableRecords.add(record);
     }
 
     // lock on mDisableRecords
@@ -1016,7 +1087,7 @@
         for (int i=0; i<N; i++) {
             final DisableRecord rec = mDisableRecords.get(i);
             if (rec.userId == userId) {
-                net |= (which == 1) ? rec.what1 : rec.what2;
+                net |= rec.getFlags(which);
             }
         }
         return net;
@@ -1036,11 +1107,7 @@
             pw.println("  mDisableRecords.size=" + N);
             for (int i=0; i<N; i++) {
                 DisableRecord tok = mDisableRecords.get(i);
-                pw.println("    [" + i + "] userId=" + tok.userId
-                                + " what1=0x" + Integer.toHexString(tok.what1)
-                                + " what2=0x" + Integer.toHexString(tok.what2)
-                                + " pkg=" + tok.pkg
-                                + " token=" + tok.token);
+                pw.println("    [" + i + "] " + tok);
             }
             pw.println("  mCurrentUserId=" + mCurrentUserId);
             pw.println("  mIcons=");
diff --git a/com/android/server/storage/AppCollector.java b/com/android/server/storage/AppCollector.java
index 03b754f..0b51f9c 100644
--- a/com/android/server/storage/AppCollector.java
+++ b/com/android/server/storage/AppCollector.java
@@ -135,7 +135,7 @@
                                 PackageStats packageStats = new PackageStats(app.packageName,
                                         user.id);
                                 packageStats.cacheSize = storageStats.getCacheBytes();
-                                packageStats.codeSize = storageStats.getCodeBytes();
+                                packageStats.codeSize = storageStats.getAppBytes();
                                 packageStats.dataSize = storageStats.getDataBytes();
                                 stats.add(packageStats);
                             } catch (NameNotFoundException | IOException e) {
diff --git a/com/android/server/storage/DiskStatsFileLogger.java b/com/android/server/storage/DiskStatsFileLogger.java
index 0094ab5..1db3ec4 100644
--- a/com/android/server/storage/DiskStatsFileLogger.java
+++ b/com/android/server/storage/DiskStatsFileLogger.java
@@ -56,10 +56,12 @@
     public static final String SYSTEM_KEY = "systemSize";
     public static final String MISC_KEY = "otherSize";
     public static final String APP_SIZE_AGG_KEY = "appSize";
+    public static final String APP_DATA_SIZE_AGG_KEY = "appDataSize";
     public static final String APP_CACHE_AGG_KEY = "cacheSize";
     public static final String PACKAGE_NAMES_KEY = "packageNames";
     public static final String APP_SIZES_KEY = "appSizes";
     public static final String APP_CACHES_KEY = "cacheSizes";
+    public static final String APP_DATA_KEY = "appDataSizes";
     public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
 
     private MeasurementResult mResult;
@@ -114,31 +116,39 @@
     private void addAppsToJson(JSONObject json) throws JSONException {
         JSONArray names = new JSONArray();
         JSONArray appSizeList = new JSONArray();
+        JSONArray appDataSizeList = new JSONArray();
         JSONArray cacheSizeList = new JSONArray();
 
         long appSizeSum = 0L;
+        long appDataSizeSum = 0L;
         long cacheSizeSum = 0L;
         boolean isExternal = Environment.isExternalStorageEmulated();
         for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
             PackageStats stat = entry.getValue();
-            long appSize = stat.codeSize + stat.dataSize;
+            long appSize = stat.codeSize;
+            long appDataSize = stat.dataSize;
             long cacheSize = stat.cacheSize;
             if (isExternal) {
-                appSize += stat.externalCodeSize + stat.externalDataSize;
+                appSize += stat.externalCodeSize;
+                appDataSize += stat.externalDataSize;
                 cacheSize += stat.externalCacheSize;
             }
             appSizeSum += appSize;
+            appDataSizeSum += appDataSize;
             cacheSizeSum += cacheSize;
 
             names.put(stat.packageName);
             appSizeList.put(appSize);
+            appDataSizeList.put(appDataSize);
             cacheSizeList.put(cacheSize);
         }
         json.put(PACKAGE_NAMES_KEY, names);
         json.put(APP_SIZES_KEY, appSizeList);
         json.put(APP_CACHES_KEY, cacheSizeList);
+        json.put(APP_DATA_KEY, appDataSizeList);
         json.put(APP_SIZE_AGG_KEY, appSizeSum);
         json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
+        json.put(APP_DATA_SIZE_AGG_KEY, appDataSizeSum);
     }
 
     /**
diff --git a/com/android/server/timezone/IntentHelper.java b/com/android/server/timezone/IntentHelper.java
index 0cb9065..5de5432 100644
--- a/com/android/server/timezone/IntentHelper.java
+++ b/com/android/server/timezone/IntentHelper.java
@@ -23,15 +23,22 @@
  */
 interface IntentHelper {
 
-    void initialize(String updateAppPackageName, String dataAppPackageName, Listener listener);
+    void initialize(String updateAppPackageName, String dataAppPackageName,
+            PackageTracker packageTracker);
 
     void sendTriggerUpdateCheck(CheckToken checkToken);
 
-    void enableReliabilityTriggering();
+    /**
+     * Schedule a "reliability trigger" after at least minimumDelayMillis, replacing any existing
+     * scheduled one. A reliability trigger ensures that the {@link PackageTracker} can pick up
+     * reliably if a previous update check did not complete for some reason. It can happen when
+     * the device is idle. The trigger is expected to call
+     * {@link PackageTracker#triggerUpdateIfNeeded(boolean)} with a {@code false} value.
+     */
+    void scheduleReliabilityTrigger(long minimumDelayMillis);
 
-    void disableReliabilityTriggering();
-
-    interface Listener {
-        void triggerUpdateIfNeeded(boolean packageUpdated);
-    }
+    /**
+     * Make sure there is no reliability trigger scheduled. No-op if there wasn't one.
+     */
+    void unscheduleReliabilityTrigger();
 }
diff --git a/com/android/server/timezone/IntentHelperImpl.java b/com/android/server/timezone/IntentHelperImpl.java
index 6db70cd..6e6259d 100644
--- a/com/android/server/timezone/IntentHelperImpl.java
+++ b/com/android/server/timezone/IntentHelperImpl.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.PatternMatcher;
+import android.os.UserHandle;
 import android.util.Slog;
 
 /**
@@ -36,16 +37,13 @@
     private final Context mContext;
     private String mUpdaterAppPackageName;
 
-    private boolean mReliabilityReceiverEnabled;
-    private Receiver mReliabilityReceiver;
-
     IntentHelperImpl(Context context) {
         mContext = context;
     }
 
     @Override
-    public void initialize(
-            String updaterAppPackageName, String dataAppPackageName, Listener listener) {
+    public void initialize(String updaterAppPackageName, String dataAppPackageName,
+            PackageTracker packageTracker) {
         mUpdaterAppPackageName = updaterAppPackageName;
 
         // Register for events of interest.
@@ -78,10 +76,10 @@
         // We do not register for ACTION_PACKAGE_DATA_CLEARED because the updater / data apps are
         // not expected to need local data.
 
-        Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
-        mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
-
-        mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
+        Receiver packageUpdateReceiver = new Receiver(packageTracker);
+        mContext.registerReceiverAsUser(
+                packageUpdateReceiver, UserHandle.SYSTEM, packageIntentFilter,
+                null /* broadcastPermission */, null /* default handler */);
     }
 
     /** Sends an intent to trigger an update check. */
@@ -93,39 +91,26 @@
     }
 
     @Override
-    public synchronized void enableReliabilityTriggering() {
-        if (!mReliabilityReceiverEnabled) {
-            // The intent filter that exists to make updates reliable in the event of failures /
-            // reboots.
-            IntentFilter reliabilityIntentFilter = new IntentFilter();
-            reliabilityIntentFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
-            mContext.registerReceiver(mReliabilityReceiver, reliabilityIntentFilter);
-            mReliabilityReceiverEnabled = true;
-        }
+    public synchronized void scheduleReliabilityTrigger(long minimumDelayMillis) {
+        TimeZoneUpdateIdler.schedule(mContext, minimumDelayMillis);
     }
 
     @Override
-    public synchronized void disableReliabilityTriggering() {
-        if (mReliabilityReceiverEnabled) {
-            mContext.unregisterReceiver(mReliabilityReceiver);
-            mReliabilityReceiverEnabled = false;
-        }
+    public synchronized void unscheduleReliabilityTrigger() {
+        TimeZoneUpdateIdler.unschedule(mContext);
     }
 
     private static class Receiver extends BroadcastReceiver {
-        private final Listener mListener;
-        private final boolean mPackageUpdated;
+        private final PackageTracker mPackageTracker;
 
-        private Receiver(Listener listener, boolean packageUpdated) {
-            mListener = listener;
-            mPackageUpdated = packageUpdated;
+        private Receiver(PackageTracker packageTracker) {
+            mPackageTracker = packageTracker;
         }
 
         @Override
         public void onReceive(Context context, Intent intent) {
             Slog.d(TAG, "Received intent: " + intent.toString());
-            mListener.triggerUpdateIfNeeded(mPackageUpdated);
+            mPackageTracker.triggerUpdateIfNeeded(true /* packageChanged */);
         }
     }
-
 }
diff --git a/com/android/server/timezone/PackageTracker.java b/com/android/server/timezone/PackageTracker.java
index 24e0fe4..f0306b9 100644
--- a/com/android/server/timezone/PackageTracker.java
+++ b/com/android/server/timezone/PackageTracker.java
@@ -51,7 +51,7 @@
  */
 // Also made non-final so it can be mocked.
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class PackageTracker implements IntentHelper.Listener {
+public class PackageTracker {
     private static final String TAG = "timezone.PackageTracker";
 
     private final PackageManagerHelper mPackageManagerHelper;
@@ -72,6 +72,13 @@
     // The number of failed checks in a row before reliability checks should stop happening.
     private long mFailedCheckRetryCount;
 
+    /*
+     * The minimum delay between a successive reliability triggers / other operations. Should to be
+     * larger than mCheckTimeAllowedMillis to avoid reliability triggers happening during package
+     * update checks.
+     */
+    private int mDelayBeforeReliabilityCheckMillis;
+
     // Reliability check state: If a check was triggered but not acknowledged within
     // mCheckTimeAllowedMillis then another one can be triggered.
     private Long mLastTriggerTimestamp = null;
@@ -122,6 +129,7 @@
         mDataAppPackageName = mConfigHelper.getDataAppPackageName();
         mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
         mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
+        mDelayBeforeReliabilityCheckMillis = mCheckTimeAllowedMillis + (60 * 1000);
 
         // Validate the device configuration including the application packages.
         // The manifest entries in the apps themselves are not validated until use as they can
@@ -135,9 +143,10 @@
         // Initialize the intent helper.
         mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
 
-        // Enable the reliability triggering so we will have at least one reliability trigger if
-        // a package isn't updated.
-        mIntentHelper.enableReliabilityTriggering();
+        // Schedule a reliability trigger so we will have at least one after boot. This will allow
+        // us to catch if a package updated wasn't handled to completion. There's no hurry: it's ok
+        // to delay for a while before doing this even if idle.
+        mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
 
         Slog.i(TAG, "Time zone updater / data package tracking enabled");
     }
@@ -195,7 +204,6 @@
      * @param packageChanged true if this method was called because a known packaged definitely
      *     changed, false if the cause is a reliability trigger
      */
-    @Override
     public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
         if (!mTrackingEnabled) {
             throw new IllegalStateException("Unexpected call. Tracking is disabled.");
@@ -212,8 +220,8 @@
                     + " updaterApp=" + updaterAppManifestValid
                     + ", dataApp=" + dataAppManifestValid);
 
-            // There's no point in doing reliability checks if the current packages are bad.
-            mIntentHelper.disableReliabilityTriggering();
+            // There's no point in doing any reliability triggers if the current packages are bad.
+            mIntentHelper.unscheduleReliabilityTrigger();
             return;
         }
 
@@ -238,7 +246,8 @@
                     Slog.d(TAG,
                             "triggerUpdateIfNeeded: checkComplete call is not yet overdue."
                                     + " Not triggering.");
-                    // Not doing any work, but also not disabling future reliability triggers.
+                    // Don't do any work now but we do schedule a future reliability trigger.
+                    mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
                     return;
                 }
             } else if (mCheckFailureCount > mFailedCheckRetryCount) {
@@ -247,13 +256,13 @@
                 Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
                         + " exceeded. Stopping reliability triggers until next reboot or package"
                         + " update.");
-                mIntentHelper.disableReliabilityTriggering();
+                mIntentHelper.unscheduleReliabilityTrigger();
                 return;
             } else if (mCheckFailureCount == 0) {
                 // Case 4.
                 Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
                         + " successful.");
-                mIntentHelper.disableReliabilityTriggering();
+                mIntentHelper.unscheduleReliabilityTrigger();
                 return;
             }
         }
@@ -263,7 +272,7 @@
         if (currentInstalledVersions == null) {
             // This should not happen if the device is configured in a valid way.
             Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
-            mIntentHelper.disableReliabilityTriggering();
+            mIntentHelper.unscheduleReliabilityTrigger();
             return;
         }
 
@@ -288,7 +297,7 @@
                 // The last check succeeded and nothing has changed. Do nothing and disable
                 // reliability checks.
                 Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
-                mIntentHelper.disableReliabilityTriggering();
+                mIntentHelper.unscheduleReliabilityTrigger();
                 return;
             }
         }
@@ -299,6 +308,8 @@
         if (checkToken == null) {
             Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
                     + " Not sending check request.");
+            // Trigger again later: perhaps we'll have better luck.
+            mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
             return;
         }
 
@@ -309,9 +320,9 @@
         // Update the reliability check state in case the update fails.
         setCheckInProgress();
 
-        // Enable reliability triggering in case the check doesn't succeed and there is no
-        // response at all. Enabling reliability triggering is idempotent.
-        mIntentHelper.enableReliabilityTriggering();
+        // Schedule a reliability trigger in case the update check doesn't succeed and there is no
+        // response at all. It will be cancelled if the check is successful in recordCheckResult.
+        mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
     }
 
     /**
@@ -370,9 +381,9 @@
                     + " storage state.");
             mPackageStatusStorage.resetCheckState();
 
-            // Enable reliability triggering and reset the failure count so we know that the
+            // Schedule a reliability trigger and reset the failure count so we know that the
             // next reliability trigger will do something.
-            mIntentHelper.enableReliabilityTriggering();
+            mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
             mCheckFailureCount = 0;
         } else {
             // This is the expected case when tracking is enabled: a check was triggered and it has
@@ -385,13 +396,13 @@
                 setCheckComplete();
 
                 if (success) {
-                    // Since the check was successful, no more reliability checks are required until
+                    // Since the check was successful, no reliability trigger is required until
                     // there is a package change.
-                    mIntentHelper.disableReliabilityTriggering();
+                    mIntentHelper.unscheduleReliabilityTrigger();
                     mCheckFailureCount = 0;
                 } else {
-                    // Enable reliability triggering to potentially check again in future.
-                    mIntentHelper.enableReliabilityTriggering();
+                    // Enable schedule a reliability trigger to check again in future.
+                    mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
                     mCheckFailureCount++;
                 }
             } else {
@@ -400,8 +411,8 @@
                 Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
                         + " with success=" + success + ". Optimistic lock failure");
 
-                // Enable reliability triggering to potentially try again in future.
-                mIntentHelper.enableReliabilityTriggering();
+                // Schedule a reliability trigger to potentially try again in future.
+                mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
                 mCheckFailureCount++;
             }
         }
@@ -515,6 +526,7 @@
                 ", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' +
                 ", mDataAppPackageName='" + mDataAppPackageName + '\'' +
                 ", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis +
+                ", mDelayBeforeReliabilityCheckMillis=" + mDelayBeforeReliabilityCheckMillis +
                 ", mFailedCheckRetryCount=" + mFailedCheckRetryCount +
                 ", mLastTriggerTimestamp=" + mLastTriggerTimestamp +
                 ", mCheckTriggered=" + mCheckTriggered +
diff --git a/com/android/server/timezone/PackageTrackerHelperImpl.java b/com/android/server/timezone/PackageTrackerHelperImpl.java
index 2e0c21b..b89dd38 100644
--- a/com/android/server/timezone/PackageTrackerHelperImpl.java
+++ b/com/android/server/timezone/PackageTrackerHelperImpl.java
@@ -26,6 +26,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import java.util.List;
@@ -114,8 +115,8 @@
     @Override
     public boolean contentProviderRegistered(String authority, String requiredPackageName) {
         int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
-        ProviderInfo providerInfo =
-                mPackageManager.resolveContentProvider(authority, flags);
+        ProviderInfo providerInfo = mPackageManager.resolveContentProviderAsUser(
+                authority, flags, UserHandle.SYSTEM.getIdentifier());
         if (providerInfo == null) {
             Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
                     + authority);
@@ -136,7 +137,8 @@
             throws PackageManager.NameNotFoundException {
 
         int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
-        List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags);
+        List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceiversAsUser(
+                intent, flags, UserHandle.SYSTEM);
         if (resolveInfo.size() != 1) {
             Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
                     + " intent=" + intent + ", found=" + resolveInfo);
diff --git a/com/android/server/timezone/RulesManagerService.java b/com/android/server/timezone/RulesManagerService.java
index 3ad4419..52b49ba 100644
--- a/com/android/server/timezone/RulesManagerService.java
+++ b/com/android/server/timezone/RulesManagerService.java
@@ -47,6 +47,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import libcore.icu.ICU;
+import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
 import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
@@ -69,18 +70,22 @@
                     DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
 
     public static class Lifecycle extends SystemService {
-        private RulesManagerService mService;
-
         public Lifecycle(Context context) {
             super(context);
         }
 
         @Override
         public void onStart() {
-            mService = RulesManagerService.create(getContext());
-            mService.start();
+            RulesManagerService service = RulesManagerService.create(getContext());
+            service.start();
 
-            publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
+            // Publish the binder service so it can be accessed from other (appropriately
+            // permissioned) processes.
+            publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, service);
+
+            // Publish the service instance locally so we can use it directly from within the system
+            // server from TimeZoneUpdateIdler.
+            publishLocalService(RulesManagerService.class, service);
         }
     }
 
@@ -475,9 +480,10 @@
                         case 'a': {
                             // Report the active rules version (i.e. the rules in use by the current
                             // process).
-                            pw.println("Active rules version (ICU, libcore): "
+                            pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
                                     + ICU.getTZDataVersion() + ","
-                                    + ZoneInfoDB.getInstance().getVersion());
+                                    + ZoneInfoDB.getInstance().getVersion() + ","
+                                    + TimeZoneFinder.getInstance().getIanaVersion());
                             break;
                         }
                         default: {
@@ -490,12 +496,24 @@
         }
 
         pw.println("RulesManagerService state: " + toString());
-        pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + ","
-                + ZoneInfoDB.getInstance().getVersion());
+        pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
+                + ICU.getTZDataVersion() + ","
+                + ZoneInfoDB.getInstance().getVersion() + ","
+                + TimeZoneFinder.getInstance().getIanaVersion());
         pw.println("Distro state: " + rulesState.toString());
         mPackageTracker.dump(pw);
     }
 
+    /**
+     * Called when the device is considered idle.
+     */
+    void notifyIdle() {
+        // No package has changed: we are just triggering because the device is idle and there
+        // *might* be work to do.
+        final boolean packageChanged = false;
+        mPackageTracker.triggerUpdateIfNeeded(packageChanged);
+    }
+
     @Override
     public String toString() {
         return "RulesManagerService{" +
diff --git a/com/android/server/timezone/TimeZoneUpdateIdler.java b/com/android/server/timezone/TimeZoneUpdateIdler.java
new file mode 100644
index 0000000..a7767a4
--- /dev/null
+++ b/com/android/server/timezone/TimeZoneUpdateIdler.java
@@ -0,0 +1,100 @@
+/*
+ * 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.timezone;
+
+import com.android.server.LocalServices;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * A JobService used to trigger time zone rules update work when a device falls idle.
+ */
+public final class TimeZoneUpdateIdler extends JobService {
+
+    private static final String TAG = "timezone.TimeZoneUpdateIdler";
+
+    /** The static job ID used to handle on-idle work. */
+    // Must be unique within UID (system service)
+    private static final int TIME_ZONE_UPDATE_IDLE_JOB_ID = 27042305;
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        RulesManagerService rulesManagerService =
+                LocalServices.getService(RulesManagerService.class);
+
+        Slog.d(TAG, "onStartJob() called");
+
+        // Note: notifyIdle() explicitly handles canceling / re-scheduling so no need to reschedule
+        // here.
+        rulesManagerService.notifyIdle();
+
+        // Everything is handled synchronously. We are done.
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        // Reschedule if stopped unless it was cancelled due to unschedule().
+        boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+        Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
+        return reschedule;
+    }
+
+    /**
+     * Schedules the TimeZoneUpdateIdler job service to run once.
+     *
+     * @param context Context to use to get a job scheduler.
+     */
+    public static void schedule(Context context, long minimumDelayMillis) {
+        // Request that the JobScheduler tell us when the device falls idle.
+        JobScheduler jobScheduler =
+                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+        // The TimeZoneUpdateIdler will send an intent that will trigger the Receiver.
+        ComponentName idlerJobServiceName =
+                new ComponentName(context, TimeZoneUpdateIdler.class);
+
+        // We require the device is idle, but also that it is charging to be as non-invasive as
+        // we can.
+        JobInfo.Builder jobInfoBuilder =
+                new JobInfo.Builder(TIME_ZONE_UPDATE_IDLE_JOB_ID, idlerJobServiceName)
+                        .setRequiresDeviceIdle(true)
+                        .setRequiresCharging(true)
+                        .setMinimumLatency(minimumDelayMillis);
+
+        Slog.d(TAG, "schedule() called: minimumDelayMillis=" + minimumDelayMillis);
+        jobScheduler.schedule(jobInfoBuilder.build());
+    }
+
+    /**
+     * Unschedules the TimeZoneUpdateIdler job service.
+     *
+     * @param context Context to use to get a job scheduler.
+     */
+    public static void unschedule(Context context) {
+        JobScheduler jobScheduler =
+                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        Slog.d(TAG, "unschedule() called");
+        jobScheduler.cancel(TIME_ZONE_UPDATE_IDLE_JOB_ID);
+    }
+}
diff --git a/com/android/server/twilight/TwilightState.java b/com/android/server/twilight/TwilightState.java
index 30a8ccc..71304a7 100644
--- a/com/android/server/twilight/TwilightState.java
+++ b/com/android/server/twilight/TwilightState.java
@@ -18,7 +18,10 @@
 
 import android.text.format.DateFormat;
 
-import java.util.Calendar;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.TimeZone;
 
 /**
  * The twilight state, consisting of the sunrise and sunset times (in millis) for the current
@@ -45,12 +48,11 @@
     }
 
     /**
-     * Returns a new {@link Calendar} instance initialized to {@link #sunriseTimeMillis()}.
+     * Returns a new {@link LocalDateTime} instance initialized to {@link #sunriseTimeMillis()}.
      */
-    public Calendar sunrise() {
-        final Calendar sunrise = Calendar.getInstance();
-        sunrise.setTimeInMillis(mSunriseTimeMillis);
-        return sunrise;
+    public LocalDateTime sunrise() {
+        final ZoneId zoneId = TimeZone.getDefault().toZoneId();
+        return LocalDateTime.ofInstant(Instant.ofEpochMilli(mSunriseTimeMillis), zoneId);
     }
 
     /**
@@ -62,12 +64,11 @@
     }
 
     /**
-     * Returns a new {@link Calendar} instance initialized to {@link #sunsetTimeMillis()}.
+     * Returns a new {@link LocalDateTime} instance initialized to {@link #sunsetTimeMillis()}.
      */
-    public Calendar sunset() {
-        final Calendar sunset = Calendar.getInstance();
-        sunset.setTimeInMillis(mSunsetTimeMillis);
-        return sunset;
+    public LocalDateTime sunset() {
+        final ZoneId zoneId = TimeZone.getDefault().toZoneId();
+        return LocalDateTime.ofInstant(Instant.ofEpochMilli(mSunsetTimeMillis), zoneId);
     }
 
     /**
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
new file mode 100644
index 0000000..b2446ba
--- /dev/null
+++ b/com/android/server/usage/AppStandbyController.java
@@ -0,0 +1,987 @@
+/**
+ * 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.usage;
+
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.IDeviceIdleController;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the standby state of an app, listening to various events.
+ */
+public class AppStandbyController {
+
+    private static final String TAG = "AppStandbyController";
+    private static final boolean DEBUG = false;
+
+    static final boolean COMPRESS_TIME = false;
+    private static final long ONE_MINUTE = 60 * 1000;
+
+    // To name the lock for stack traces
+    static class Lock {}
+
+    /** Lock to protect the app's standby state. Required for calls into AppIdleHistory */
+    private final Object mAppIdleLock = new Lock();
+
+    /** Keeps the history and state for each app. */
+    @GuardedBy("mAppIdleLock")
+    private AppIdleHistory mAppIdleHistory;
+
+    @GuardedBy("mAppIdleLock")
+    private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+            mPackageAccessListeners = new ArrayList<>();
+
+    /** Whether we've queried the list of carrier privileged apps. */
+    @GuardedBy("mAppIdleLock")
+    private boolean mHaveCarrierPrivilegedApps;
+
+    /** List of carrier-privileged apps that should be excluded from standby */
+    @GuardedBy("mAppIdleLock")
+    private List<String> mCarrierPrivilegedApps;
+
+    // Messages for the handler
+    static final int MSG_INFORM_LISTENERS = 3;
+    static final int MSG_FORCE_IDLE_STATE = 4;
+    static final int MSG_CHECK_IDLE_STATES = 5;
+    static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
+    static final int MSG_PAROLE_END_TIMEOUT = 7;
+    static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
+    static final int MSG_PAROLE_STATE_CHANGED = 9;
+    static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
+
+    long mAppIdleScreenThresholdMillis;
+    long mCheckIdleIntervalMillis;
+    long mAppIdleWallclockThresholdMillis;
+    long mAppIdleParoleIntervalMillis;
+    long mAppIdleParoleDurationMillis;
+    boolean mAppIdleEnabled;
+    boolean mAppIdleTempParoled;
+    boolean mCharging;
+    private long mLastAppIdleParoledTime;
+    private boolean mSystemServicesReady = false;
+
+    private volatile boolean mPendingOneTimeCheckIdleStates;
+
+    private final Handler mHandler;
+    private final Context mContext;
+
+    private DisplayManager mDisplayManager;
+    private IDeviceIdleController mDeviceIdleController;
+    private AppWidgetManager mAppWidgetManager;
+    private IBatteryStats mBatteryStats;
+    private PowerManager mPowerManager;
+    private PackageManager mPackageManager;
+    private PackageManagerInternal mPackageManagerInternal;
+
+    AppStandbyController(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new AppStandbyHandler(looper);
+        mPackageManager = mContext.getPackageManager();
+        mAppIdleEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableAutoPowerModes);
+        if (mAppIdleEnabled) {
+            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+            mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
+        }
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
+        }
+
+        IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme("package");
+
+        mContext.registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
+                null, mHandler);
+    }
+
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            // Observe changes to the threshold
+            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+            settingsObserver.registerObserver();
+            settingsObserver.updateSettings();
+
+            mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+            mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+            mBatteryStats = IBatteryStats.Stub.asInterface(
+                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
+            mDisplayManager = (DisplayManager) mContext.getSystemService(
+                    Context.DISPLAY_SERVICE);
+            mPowerManager = mContext.getSystemService(PowerManager.class);
+            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+
+            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+            synchronized (mAppIdleLock) {
+                mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
+            }
+
+            if (mPendingOneTimeCheckIdleStates) {
+                postOneTimeCheckIdleStates();
+            }
+
+            mSystemServicesReady = true;
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            setChargingState(mContext.getSystemService(BatteryManager.class).isCharging());
+        }
+    }
+
+    void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
+        // Get sync adapters for the authority
+        String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
+                authority, userId);
+        for (String packageName: packages) {
+            // Only force the sync adapters to active if the provider is not in the same package and
+            // the sync adapter is a system package.
+            try {
+                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
+                        packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
+                if (pi == null || pi.applicationInfo == null) {
+                    continue;
+                }
+                if (!packageName.equals(providerPkgName)) {
+                    setAppIdleAsync(packageName, false, userId);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen
+            }
+        }
+    }
+
+    void setChargingState(boolean charging) {
+        synchronized (mAppIdleLock) {
+            if (mCharging != charging) {
+                mCharging = charging;
+                postParoleStateChanged();
+            }
+        }
+    }
+
+    /** Paroled here means temporary pardon from being inactive */
+    void setAppIdleParoled(boolean paroled) {
+        synchronized (mAppIdleLock) {
+            final long now = System.currentTimeMillis();
+            if (mAppIdleTempParoled != paroled) {
+                mAppIdleTempParoled = paroled;
+                if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
+                if (paroled) {
+                    postParoleEndTimeout();
+                } else {
+                    mLastAppIdleParoledTime = now;
+                    postNextParoleTimeout(now);
+                }
+                postParoleStateChanged();
+            }
+        }
+    }
+
+    boolean isParoledOrCharging() {
+        synchronized (mAppIdleLock) {
+            return mAppIdleTempParoled || mCharging;
+        }
+    }
+
+    private void postNextParoleTimeout(long now) {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
+        mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
+        // Compute when the next parole needs to happen. We check more frequently than necessary
+        // since the message handler delays are based on elapsedRealTime and not wallclock time.
+        // The comparison is done in wallclock time.
+        long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
+        if (timeLeft < 0) {
+            timeLeft = 0;
+        }
+        mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
+    }
+
+    private void postParoleEndTimeout() {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
+        mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
+        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
+    }
+
+    private void postParoleStateChanged() {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
+        mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
+        mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
+    }
+
+    void postCheckIdleStates(int userId) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
+    }
+
+    /**
+     * We send a different message to check idle states once, otherwise we would end up
+     * scheduling a series of repeating checkIdleStates each time we fired off one.
+     */
+    void postOneTimeCheckIdleStates() {
+        if (mDeviceIdleController == null) {
+            // Not booted yet; wait for it!
+            mPendingOneTimeCheckIdleStates = true;
+        } else {
+            mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
+            mPendingOneTimeCheckIdleStates = false;
+        }
+    }
+
+    /**
+     * Check all running users' or specified user's apps to see if they enter an idle state.
+     * @return Returns whether checking should continue periodically.
+     */
+    boolean checkIdleStates(int checkUserId) {
+        if (!mAppIdleEnabled) {
+            return false;
+        }
+
+        final int[] runningUserIds;
+        try {
+            runningUserIds = ActivityManager.getService().getRunningUserIds();
+            if (checkUserId != UserHandle.USER_ALL
+                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
+                return false;
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        for (int i = 0; i < runningUserIds.length; i++) {
+            final int userId = runningUserIds[i];
+            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
+                continue;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Checking idle state for user " + userId);
+            }
+            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                    PackageManager.MATCH_DISABLED_COMPONENTS,
+                    userId);
+            final int packageCount = packages.size();
+            for (int p = 0; p < packageCount; p++) {
+                final PackageInfo pi = packages.get(p);
+                final String packageName = pi.packageName;
+                final boolean isIdle = isAppIdleFiltered(packageName,
+                        UserHandle.getAppId(pi.applicationInfo.uid),
+                        userId, elapsedRealtime);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                        userId, isIdle ? 1 : 0, packageName));
+                if (isIdle) {
+                    synchronized (mAppIdleLock) {
+                        mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
+                    }
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "checkIdleStates took "
+                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
+        return true;
+    }
+
+    /** Check if it's been a while since last parole and let idle apps do some work */
+    void checkParoleTimeout() {
+        boolean setParoled = false;
+        synchronized (mAppIdleLock) {
+            final long now = System.currentTimeMillis();
+            if (!mAppIdleTempParoled) {
+                final long timeSinceLastParole = now - mLastAppIdleParoledTime;
+                if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
+                    if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
+                    setParoled = true;
+                } else {
+                    if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
+                    postNextParoleTimeout(now);
+                }
+            }
+        }
+        if (setParoled) {
+            setAppIdleParoled(true);
+        }
+    }
+
+    private void notifyBatteryStats(String packageName, int userId, boolean idle) {
+        try {
+            final int uid = mPackageManager.getPackageUidAsUser(packageName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+            if (idle) {
+                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+                        packageName, uid);
+            } else {
+                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+                        packageName, uid);
+            }
+        } catch (PackageManager.NameNotFoundException | RemoteException e) {
+        }
+    }
+
+    void onDeviceIdleModeChanged() {
+        final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
+        if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
+        boolean paroled = false;
+        synchronized (mAppIdleLock) {
+            final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
+            if (!deviceIdle
+                    && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
+                }
+                paroled = true;
+            } else if (deviceIdle) {
+                if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
+                paroled = false;
+            } else {
+                return;
+            }
+        }
+        setAppIdleParoled(paroled);
+    }
+
+    void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
+        synchronized (mAppIdleLock) {
+            // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
+            // about apps that are on some kind of whitelist anyway.
+            final boolean previouslyIdle = mAppIdleHistory.isIdle(
+                    event.mPackage, userId, elapsedRealtime);
+            // Inform listeners if necessary
+            if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
+                    || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
+                    || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
+                    || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
+                mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
+                if (previouslyIdle) {
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+                                /* idle = */ 0, event.mPackage));
+                    notifyBatteryStats(event.mPackage, userId, false);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
+     * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
+     * the threshold for idle.
+     *
+     * This method is always called from the handler thread, so not much synchronization is
+     * required.
+     */
+    void forceIdleState(String packageName, int userId, boolean idle) {
+        final int appId = getAppId(packageName);
+        if (appId < 0) return;
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+
+        final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
+                userId, elapsedRealtime);
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
+        }
+        final boolean stillIdle = isAppIdleFiltered(packageName, appId,
+                userId, elapsedRealtime);
+        // Inform listeners if necessary
+        if (previouslyIdle != stillIdle) {
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+                    /* idle = */ stillIdle ? 1 : 0, packageName));
+            if (!stillIdle) {
+                notifyBatteryStats(packageName, userId, idle);
+            }
+        }
+    }
+
+    public void onUserRemoved(int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.onUserRemoved(userId);
+        }
+    }
+
+    private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
+        synchronized (mAppIdleLock) {
+            return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
+        }
+    }
+
+    void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+        synchronized (mAppIdleLock) {
+            if (!mPackageAccessListeners.contains(listener)) {
+                mPackageAccessListeners.add(listener);
+            }
+        }
+    }
+
+    void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+        synchronized (mAppIdleLock) {
+            mPackageAccessListeners.remove(listener);
+        }
+    }
+
+    int getAppId(String packageName) {
+        try {
+            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
+                    PackageManager.MATCH_ANY_USER
+                            | PackageManager.MATCH_DISABLED_COMPONENTS);
+            return ai.uid;
+        } catch (PackageManager.NameNotFoundException re) {
+            return -1;
+        }
+    }
+
+    boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+            boolean shouldObfuscateInstantApps) {
+        if (isParoledOrCharging()) {
+            return false;
+        }
+        if (shouldObfuscateInstantApps &&
+                mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
+            return false;
+        }
+        return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
+    }
+
+    /**
+     * Checks if an app has been idle for a while and filters out apps that are excluded.
+     * It returns false if the current system state allows all apps to be considered active.
+     * This happens if the device is plugged in or temporarily allowed to make exceptions.
+     * Called by interface impls.
+     */
+    boolean isAppIdleFiltered(String packageName, int appId, int userId,
+            long elapsedRealtime) {
+        if (packageName == null) return false;
+        // If not enabled at all, of course nobody is ever idle.
+        if (!mAppIdleEnabled) {
+            return false;
+        }
+        if (appId < Process.FIRST_APPLICATION_UID) {
+            // System uids never go idle.
+            return false;
+        }
+        if (packageName.equals("android")) {
+            // Nor does the framework (which should be redundant with the above, but for MR1 we will
+            // retain this for safety).
+            return false;
+        }
+        if (mSystemServicesReady) {
+            try {
+                // We allow all whitelisted apps, including those that don't want to be whitelisted
+                // for idle mode, because app idle (aka app standby) is really not as big an issue
+                // for controlling who participates vs. doze mode.
+                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+                    return false;
+                }
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+
+            if (isActiveDeviceAdmin(packageName, userId)) {
+                return false;
+            }
+
+            if (isActiveNetworkScorer(packageName)) {
+                return false;
+            }
+
+            if (mAppWidgetManager != null
+                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
+                return false;
+            }
+
+            if (isDeviceProvisioningPackage(packageName)) {
+                return false;
+            }
+        }
+
+        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
+            return false;
+        }
+
+        // Check this last, as it is the most expensive check
+        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
+        if (isCarrierApp(packageName)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    int[] getIdleUidsForUser(int userId) {
+        if (!mAppIdleEnabled) {
+            return new int[0];
+        }
+
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+
+        List<ApplicationInfo> apps;
+        try {
+            ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
+                    .getInstalledApplications(/* flags= */ 0, userId);
+            if (slice == null) {
+                return new int[0];
+            }
+            apps = slice.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
+        // associated with that uid, upper 16 bits is the number of those apps that is idle.
+        SparseIntArray uidStates = new SparseIntArray();
+
+        // Now resolve all app state.  Iterating over all apps, keeping track of how many
+        // we find for each uid and how many of those are idle.
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            ApplicationInfo ai = apps.get(i);
+
+            // Check whether this app is idle.
+            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
+                    userId, elapsedRealtime);
+
+            int index = uidStates.indexOfKey(ai.uid);
+            if (index < 0) {
+                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
+            } else {
+                int value = uidStates.valueAt(index);
+                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
+        int numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                numIdle++;
+            }
+        }
+
+        int[] res = new int[numIdle];
+        numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                res[numIdle] = uidStates.keyAt(i);
+                numIdle++;
+            }
+        }
+
+        return res;
+    }
+
+    void setAppIdleAsync(String packageName, boolean idle, int userId) {
+        if (packageName == null) return;
+
+        mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
+                .sendToTarget();
+    }
+
+    private boolean isActiveDeviceAdmin(String packageName, int userId) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (dpm == null) return false;
+        return dpm.packageHasActiveAdmins(packageName, userId);
+    }
+
+    /**
+     * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
+     * returns {@code false}.
+     */
+    private boolean isDeviceProvisioningPackage(String packageName) {
+        String deviceProvisioningPackage = mContext.getResources().getString(
+                com.android.internal.R.string.config_deviceProvisioningPackage);
+        return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
+    }
+
+    private boolean isCarrierApp(String packageName) {
+        synchronized (mAppIdleLock) {
+            if (!mHaveCarrierPrivilegedApps) {
+                fetchCarrierPrivilegedAppsLA();
+            }
+            if (mCarrierPrivilegedApps != null) {
+                return mCarrierPrivilegedApps.contains(packageName);
+            }
+            return false;
+        }
+    }
+
+    void clearCarrierPrivilegedApps() {
+        if (DEBUG) {
+            Slog.i(TAG, "Clearing carrier privileged apps list");
+        }
+        synchronized (mAppIdleLock) {
+            mHaveCarrierPrivilegedApps = false;
+            mCarrierPrivilegedApps = null; // Need to be refetched.
+        }
+    }
+
+    @GuardedBy("mAppIdleLock")
+    private void fetchCarrierPrivilegedAppsLA() {
+        TelephonyManager telephonyManager =
+                mContext.getSystemService(TelephonyManager.class);
+        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
+        mHaveCarrierPrivilegedApps = true;
+        if (DEBUG) {
+            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
+        }
+    }
+
+    private boolean isActiveNetworkScorer(String packageName) {
+        NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
+                Context.NETWORK_SCORE_SERVICE);
+        return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
+    }
+
+    void informListeners(String packageName, int userId, boolean isIdle) {
+        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+            listener.onAppIdleStateChanged(packageName, userId, isIdle);
+        }
+    }
+
+    void informParoleStateChanged() {
+        final boolean paroled = isParoledOrCharging();
+        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+            listener.onParoleStateChanged(paroled);
+        }
+    }
+
+    void flushToDisk(int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.writeAppIdleTimes(userId);
+        }
+    }
+
+    void flushDurationsToDisk() {
+        // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
+        // considered not-idle, which is the safest outcome in such an event.
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.writeAppIdleDurations();
+        }
+    }
+
+    boolean isDisplayOn() {
+        return mDisplayManager
+                .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+    }
+
+    void clearAppIdleForPackage(String packageName, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.clearUsage(packageName, userId);
+        }
+    }
+
+    private class PackageReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                clearCarrierPrivilegedApps();
+            }
+            if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
+                    Intent.ACTION_PACKAGE_ADDED.equals(action))
+                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
+                        getSendingUserId());
+            }
+        }
+    }
+
+    void initializeDefaultsForSystemApps(int userId) {
+        Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                PackageManager.MATCH_DISABLED_COMPONENTS,
+                userId);
+        final int packageCount = packages.size();
+        synchronized (mAppIdleLock) {
+            for (int i = 0; i < packageCount; i++) {
+                final PackageInfo pi = packages.get(i);
+                String packageName = pi.packageName;
+                if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
+                    mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
+                }
+            }
+        }
+    }
+
+    void postReportContentProviderUsage(String name, String packageName, int userId) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = name;
+        args.arg2 = packageName;
+        args.arg3 = userId;
+        mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
+                .sendToTarget();
+    }
+
+    void dumpHistory(IndentingPrintWriter idpw, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.dumpHistory(idpw, userId);
+        }
+    }
+
+    void dumpUser(IndentingPrintWriter idpw, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.dump(idpw, userId);
+        }
+    }
+
+    void dumpState(String[] args, PrintWriter pw) {
+        synchronized (mAppIdleLock) {
+            pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
+                    + "): " + mCarrierPrivilegedApps);
+        }
+
+        pw.println();
+        pw.println("Settings:");
+
+        pw.print("  mAppIdleDurationMillis=");
+        TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleWallclockThresholdMillis=");
+        TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
+        pw.println();
+
+        pw.print("  mCheckIdleIntervalMillis=");
+        TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleParoleIntervalMillis=");
+        TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleParoleDurationMillis=");
+        TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
+        pw.println();
+
+        pw.println();
+        pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+        pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
+        pw.print(" mCharging="); pw.print(mCharging);
+        pw.print(" mLastAppIdleParoledTime=");
+        TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
+        pw.println();
+    }
+
+    class AppStandbyHandler extends Handler {
+
+        AppStandbyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_FORCE_IDLE_STATE:
+                    forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    break;
+
+                case MSG_CHECK_IDLE_STATES:
+                    if (checkIdleStates(msg.arg1)) {
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
+                                mCheckIdleIntervalMillis);
+                    }
+                    break;
+
+                case MSG_ONE_TIME_CHECK_IDLE_STATES:
+                    mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
+                    checkIdleStates(UserHandle.USER_ALL);
+                    break;
+
+                case MSG_CHECK_PAROLE_TIMEOUT:
+                    checkParoleTimeout();
+                    break;
+
+                case MSG_PAROLE_END_TIMEOUT:
+                    if (DEBUG) Slog.d(TAG, "Ending parole");
+                    setAppIdleParoled(false);
+                    break;
+
+                case MSG_REPORT_CONTENT_PROVIDER_USAGE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    reportContentProviderUsage((String) args.arg1, // authority name
+                            (String) args.arg2, // package name
+                            (int) args.arg3); // userId
+                    args.recycle();
+                    break;
+
+                case MSG_PAROLE_STATE_CHANGED:
+                    if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
+                            + ", Charging state:" + mCharging);
+                    informParoleStateChanged();
+                    break;
+                default:
+                    super.handleMessage(msg);
+                    break;
+
+            }
+        }
+    };
+
+    private class DeviceStateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+                setChargingState(intent.getIntExtra("plugged", 0) != 0);
+            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+                onDeviceIdleModeChanged();
+            }
+        }
+    }
+
+    private final DisplayManager.DisplayListener mDisplayListener
+            = new DisplayManager.DisplayListener() {
+
+        @Override public void onDisplayAdded(int displayId) {
+        }
+
+        @Override public void onDisplayRemoved(int displayId) {
+        }
+
+        @Override public void onDisplayChanged(int displayId) {
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                final boolean displayOn = isDisplayOn();
+                synchronized (mAppIdleLock) {
+                    mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
+                }
+            }
+        }
+    };
+
+    /**
+     * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
+     */
+    private class SettingsObserver extends ContentObserver {
+        /**
+         * This flag has been used to disable app idle on older builds with bug b/26355386.
+         */
+        @Deprecated
+        private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
+
+        private static final String KEY_IDLE_DURATION = "idle_duration2";
+        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
+        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
+        private static final String KEY_PAROLE_DURATION = "parole_duration";
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void registerObserver() {
+            mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            updateSettings();
+            postOneTimeCheckIdleStates();
+        }
+
+        void updateSettings() {
+            synchronized (mAppIdleLock) {
+                // Look at global settings for this.
+                // TODO: Maybe apply different thresholds for different users.
+                try {
+                    mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+                            Settings.Global.APP_IDLE_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
+                    // fallthrough, mParser is empty and all defaults will be returned.
+                }
+
+                // Default: 12 hours of screen-on time sans dream-time
+                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
+                        COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
+
+                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
+                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
+
+                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
+                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
+
+                // Default: 24 hours between paroles
+                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
+                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
+
+                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
+                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
+                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
+                        mAppIdleScreenThresholdMillis);
+            }
+        }
+    }
+
+}
+
diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java
index 25e471c..afafea1 100644
--- a/com/android/server/usage/UsageStatsService.java
+++ b/com/android/server/usage/UsageStatsService.java
@@ -18,37 +18,24 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
-import android.app.admin.DevicePolicyManager;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-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.UserInfo;
 import android.content.res.Configuration;
-import android.database.ContentObserver;
-import android.hardware.display.DisplayManager;
-import android.net.NetworkScoreManager;
-import android.os.BatteryManager;
-import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -56,7 +43,6 @@
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -64,21 +50,12 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
 import android.util.ArraySet;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.view.Display;
 
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
@@ -88,7 +65,6 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -107,7 +83,6 @@
     static final boolean COMPRESS_TIME = false;
 
     private static final long TEN_SECONDS = 10 * 1000;
-    private static final long ONE_MINUTE = 60 * 1000;
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
     private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
     private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
@@ -115,24 +90,10 @@
     private static final boolean ENABLE_KERNEL_UPDATES = true;
     private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
 
-    long mAppIdleScreenThresholdMillis;
-    long mCheckIdleIntervalMillis;
-    long mAppIdleWallclockThresholdMillis;
-    long mAppIdleParoleIntervalMillis;
-    long mAppIdleParoleDurationMillis;
-
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
     static final int MSG_FLUSH_TO_DISK = 1;
     static final int MSG_REMOVE_USER = 2;
-    static final int MSG_INFORM_LISTENERS = 3;
-    static final int MSG_FORCE_IDLE_STATE = 4;
-    static final int MSG_CHECK_IDLE_STATES = 5;
-    static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
-    static final int MSG_PAROLE_END_TIMEOUT = 7;
-    static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
-    static final int MSG_PAROLE_STATE_CHANGED = 9;
-    static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -140,11 +101,7 @@
     UserManager mUserManager;
     PackageManager mPackageManager;
     PackageManagerInternal mPackageManagerInternal;
-    AppWidgetManager mAppWidgetManager;
     IDeviceIdleController mDeviceIdleController;
-    private DisplayManager mDisplayManager;
-    private PowerManager mPowerManager;
-    private IBatteryStats mBatteryStats;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
@@ -152,26 +109,7 @@
     long mRealTimeSnapshot;
     long mSystemTimeSnapshot;
 
-    boolean mAppIdleEnabled;
-    boolean mAppIdleTempParoled;
-    boolean mCharging;
-    private long mLastAppIdleParoledTime;
-
-    private volatile boolean mPendingOneTimeCheckIdleStates;
-    private boolean mSystemServicesReady = false;
-
-    private final Object mAppIdleLock = new Object();
-    @GuardedBy("mAppIdleLock")
-    private AppIdleHistory mAppIdleHistory;
-
-    @GuardedBy("mAppIdleLock")
-    private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
-            mPackageAccessListeners = new ArrayList<>();
-
-    @GuardedBy("mAppIdleLock")
-    private boolean mHaveCarrierPrivilegedApps;
-    @GuardedBy("mAppIdleLock")
-    private List<String> mCarrierPrivilegedApps;
+    AppStandbyController mAppStandby;
 
     public UsageStatsService(Context context) {
         super(context);
@@ -185,6 +123,8 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mHandler = new H(BackgroundThread.get().getLooper());
 
+        mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
+
         File systemDataDir = new File(Environment.getDataDirectory(), "system");
         mUsageStatsDir = new File(systemDataDir, "usagestats");
         mUsageStatsDir.mkdirs();
@@ -198,30 +138,9 @@
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
                 null, mHandler);
 
-        IntentFilter packageFilter = new IntentFilter();
-        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        packageFilter.addDataScheme("package");
-
-        getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
-                null, mHandler);
-
-        mAppIdleEnabled = getContext().getResources().getBoolean(
-                com.android.internal.R.bool.config_enableAutoPowerModes);
-        if (mAppIdleEnabled) {
-            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
-            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            getContext().registerReceiver(new DeviceStateReceiver(), deviceStates);
-        }
-
         synchronized (mLock) {
             cleanUpRemovedUsersLocked();
         }
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
-        }
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
@@ -233,28 +152,10 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            // Observe changes to the threshold
-            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
-            settingsObserver.registerObserver();
-            settingsObserver.updateSettings();
+            mAppStandby.onBootPhase(phase);
 
-            mAppWidgetManager = getContext().getSystemService(AppWidgetManager.class);
             mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                     ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-            mBatteryStats = IBatteryStats.Stub.asInterface(
-                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
-            mDisplayManager = (DisplayManager) getContext().getSystemService(
-                    Context.DISPLAY_SERVICE);
-            mPowerManager = getContext().getSystemService(PowerManager.class);
-
-            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
-            }
-
-            if (mPendingOneTimeCheckIdleStates) {
-                postOneTimeCheckIdleStates();
-            }
 
             if (ENABLE_KERNEL_UPDATES && KERNEL_COUNTER_FILE.exists()) {
                 try {
@@ -268,18 +169,9 @@
             } else {
                 Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
             }
-
-            mSystemServicesReady = true;
-        } else if (phase == PHASE_BOOT_COMPLETED) {
-            setChargingState(getContext().getSystemService(BatteryManager.class).isCharging());
         }
     }
 
-    private boolean isDisplayOn() {
-        return mDisplayManager
-                .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
-    }
-
     private class UserActionsReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -291,60 +183,12 @@
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >=0) {
-                    postCheckIdleStates(userId);
+                    mAppStandby.postCheckIdleStates(userId);
                 }
             }
         }
     }
 
-    private class PackageReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
-                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                clearCarrierPrivilegedApps();
-            }
-            if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_ADDED.equals(action))
-                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
-                        getSendingUserId());
-            }
-        }
-    }
-
-    private class DeviceStateReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
-                setChargingState(intent.getIntExtra("plugged", 0) != 0);
-            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
-                onDeviceIdleModeChanged();
-            }
-        }
-    }
-
-    private final DisplayManager.DisplayListener mDisplayListener
-            = new DisplayManager.DisplayListener() {
-
-        @Override public void onDisplayAdded(int displayId) {
-        }
-
-        @Override public void onDisplayRemoved(int displayId) {
-        }
-
-        @Override public void onDisplayChanged(int displayId) {
-            if (displayId == Display.DEFAULT_DISPLAY) {
-                final boolean displayOn = isDisplayOn();
-                synchronized (UsageStatsService.this.mAppIdleLock) {
-                    mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
-                }
-            }
-        }
-    };
-
     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
@@ -388,42 +232,18 @@
 
     @Override
     public void onStatsReloaded() {
-        postOneTimeCheckIdleStates();
+        mAppStandby.postOneTimeCheckIdleStates();
     }
 
     @Override
     public void onNewUpdate(int userId) {
-        initializeDefaultsForSystemApps(userId);
-    }
-
-    private void initializeDefaultsForSystemApps(int userId) {
-        Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                PackageManager.MATCH_DISABLED_COMPONENTS,
-                userId);
-        final int packageCount = packages.size();
-        synchronized (mAppIdleLock) {
-            for (int i = 0; i < packageCount; i++) {
-                final PackageInfo pi = packages.get(i);
-                String packageName = pi.packageName;
-                if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
-                    mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
-                }
-            }
-        }
+        mAppStandby.initializeDefaultsForSystemApps(userId);
     }
 
     private boolean shouldObfuscateInstantAppsForCaller(int callingUid, int userId) {
         return !mPackageManagerInternal.canAccessInstantApps(callingUid, userId);
     }
 
-    void clearAppIdleForPackage(String packageName, int userId) {
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.clearUsage(packageName, userId);
-        }
-    }
-
     private void cleanUpRemovedUsersLocked() {
         final List<UserInfo> users = mUserManager.getUsers(true);
         if (users == null || users.size() == 0) {
@@ -451,195 +271,6 @@
         }
     }
 
-    void setChargingState(boolean charging) {
-        synchronized (mAppIdleLock) {
-            if (mCharging != charging) {
-                mCharging = charging;
-                postParoleStateChanged();
-            }
-        }
-    }
-
-    /** Paroled here means temporary pardon from being inactive */
-    void setAppIdleParoled(boolean paroled) {
-        synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
-            if (mAppIdleTempParoled != paroled) {
-                mAppIdleTempParoled = paroled;
-                if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
-                if (paroled) {
-                    postParoleEndTimeout();
-                } else {
-                    mLastAppIdleParoledTime = now;
-                    postNextParoleTimeout(now);
-                }
-                postParoleStateChanged();
-            }
-        }
-    }
-
-    boolean isParoledOrCharging() {
-        synchronized (mAppIdleLock) {
-            return mAppIdleTempParoled || mCharging;
-        }
-    }
-
-    private void postNextParoleTimeout(long now) {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
-        mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
-        // Compute when the next parole needs to happen. We check more frequently than necessary
-        // since the message handler delays are based on elapsedRealTime and not wallclock time.
-        // The comparison is done in wallclock time.
-        long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
-        if (timeLeft < 0) {
-            timeLeft = 0;
-        }
-        mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
-    }
-
-    private void postParoleEndTimeout() {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
-        mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
-        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
-    }
-
-    private void postParoleStateChanged() {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
-        mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
-        mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
-    }
-
-    void postCheckIdleStates(int userId) {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
-    }
-
-    /**
-     * We send a different message to check idle states once, otherwise we would end up
-     * scheduling a series of repeating checkIdleStates each time we fired off one.
-     */
-    void postOneTimeCheckIdleStates() {
-        if (mDeviceIdleController == null) {
-            // Not booted yet; wait for it!
-            mPendingOneTimeCheckIdleStates = true;
-        } else {
-            mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
-            mPendingOneTimeCheckIdleStates = false;
-        }
-    }
-
-    /**
-     * Check all running users' or specified user's apps to see if they enter an idle state.
-     * @return Returns whether checking should continue periodically.
-     */
-    boolean checkIdleStates(int checkUserId) {
-        if (!mAppIdleEnabled) {
-            return false;
-        }
-
-        final int[] runningUserIds;
-        try {
-            runningUserIds = ActivityManager.getService().getRunningUserIds();
-            if (checkUserId != UserHandle.USER_ALL
-                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
-                return false;
-            }
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        for (int i = 0; i < runningUserIds.length; i++) {
-            final int userId = runningUserIds[i];
-            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
-                continue;
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Checking idle state for user " + userId);
-            }
-            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                    PackageManager.MATCH_DISABLED_COMPONENTS,
-                    userId);
-            final int packageCount = packages.size();
-            for (int p = 0; p < packageCount; p++) {
-                final PackageInfo pi = packages.get(p);
-                final String packageName = pi.packageName;
-                final boolean isIdle = isAppIdleFiltered(packageName,
-                        UserHandle.getAppId(pi.applicationInfo.uid),
-                        userId, elapsedRealtime);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        userId, isIdle ? 1 : 0, packageName));
-                if (isIdle) {
-                    synchronized (mAppIdleLock) {
-                        mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
-                    }
-                }
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "checkIdleStates took "
-                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
-        }
-        return true;
-    }
-
-    /** Check if it's been a while since last parole and let idle apps do some work */
-    void checkParoleTimeout() {
-        boolean setParoled = false;
-        synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
-            if (!mAppIdleTempParoled) {
-                final long timeSinceLastParole = now - mLastAppIdleParoledTime;
-                if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
-                    if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
-                    setParoled = true;
-                } else {
-                    if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
-                    postNextParoleTimeout(now);
-                }
-            }
-        }
-        if (setParoled) {
-            setAppIdleParoled(true);
-        }
-    }
-
-    private void notifyBatteryStats(String packageName, int userId, boolean idle) {
-        try {
-            final int uid = mPackageManager.getPackageUidAsUser(packageName,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
-            if (idle) {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
-                        packageName, uid);
-            } else {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
-                        packageName, uid);
-            }
-        } catch (NameNotFoundException | RemoteException e) {
-        }
-    }
-
-    void onDeviceIdleModeChanged() {
-        final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
-        if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
-        boolean paroled = false;
-        synchronized (mAppIdleLock) {
-            final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
-            if (!deviceIdle
-                    && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
-                if (DEBUG) {
-                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
-                }
-                paroled = true;
-            } else if (deviceIdle) {
-                if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
-                paroled = false;
-            } else {
-                return;
-            }
-        }
-        setAppIdleParoled(paroled);
-    }
-
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -724,76 +355,7 @@
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
             service.reportEvent(event);
 
-            synchronized (mAppIdleLock) {
-                // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
-                // about apps that are on some kind of whitelist anyway.
-                final boolean previouslyIdle = mAppIdleHistory.isIdle(
-                        event.mPackage, userId, elapsedRealtime);
-                // Inform listeners if necessary
-                if ((event.mEventType == Event.MOVE_TO_FOREGROUND
-                        || event.mEventType == Event.MOVE_TO_BACKGROUND
-                        || event.mEventType == Event.SYSTEM_INTERACTION
-                        || event.mEventType == Event.USER_INTERACTION)) {
-                    mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
-                    if (previouslyIdle) {
-                        mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                                /* idle = */ 0, event.mPackage));
-                        notifyBatteryStats(event.mPackage, userId, false);
-                    }
-                }
-            }
-        }
-    }
-
-    void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
-        // Get sync adapters for the authority
-        String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
-                authority, userId);
-        for (String packageName: packages) {
-            // Only force the sync adapters to active if the provider is not in the same package and
-            // the sync adapter is a system package.
-            try {
-                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
-                        packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
-                if (pi == null || pi.applicationInfo == null) {
-                    continue;
-                }
-                if (!packageName.equals(providerPkgName)) {
-                    setAppIdleAsync(packageName, false, userId);
-                }
-            } catch (NameNotFoundException e) {
-                // Shouldn't happen
-            }
-        }
-    }
-
-    /**
-     * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
-     * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
-     * the threshold for idle.
-     *
-     * This method is always called from the handler thread, so not much synchronization is
-     * required.
-     */
-    void forceIdleState(String packageName, int userId, boolean idle) {
-        final int appId = getAppId(packageName);
-        if (appId < 0) return;
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-
-        final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
-                userId, elapsedRealtime);
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
-        }
-        final boolean stillIdle = isAppIdleFiltered(packageName, appId,
-                userId, elapsedRealtime);
-        // Inform listeners if necessary
-        if (previouslyIdle != stillIdle) {
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                    /* idle = */ stillIdle ? 1 : 0, packageName));
-            if (!stillIdle) {
-                notifyBatteryStats(packageName, userId, idle);
-            }
+            mAppStandby.reportEvent(event, elapsedRealtime, userId);
         }
     }
 
@@ -813,9 +375,7 @@
         synchronized (mLock) {
             Slog.i(TAG, "Removing user " + userId + " and all data.");
             mUserState.remove(userId);
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.onUserRemoved(userId);
-            }
+            mAppStandby.onUserRemoved(userId);
             cleanUpRemovedUsersLocked();
         }
     }
@@ -887,253 +447,6 @@
         }
     }
 
-    private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
-        synchronized (mAppIdleLock) {
-            return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
-        }
-    }
-
-    void addListener(AppIdleStateChangeListener listener) {
-        synchronized (mAppIdleLock) {
-            if (!mPackageAccessListeners.contains(listener)) {
-                mPackageAccessListeners.add(listener);
-            }
-        }
-    }
-
-    void removeListener(AppIdleStateChangeListener listener) {
-        synchronized (mAppIdleLock) {
-            mPackageAccessListeners.remove(listener);
-        }
-    }
-
-    int getAppId(String packageName) {
-        try {
-            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
-                    PackageManager.MATCH_ANY_USER
-                            | PackageManager.MATCH_DISABLED_COMPONENTS);
-            return ai.uid;
-        } catch (NameNotFoundException re) {
-            return -1;
-        }
-    }
-
-    boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
-            boolean shouldObfuscateInstantApps) {
-        if (isParoledOrCharging()) {
-            return false;
-        }
-        if (shouldObfuscateInstantApps &&
-                mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
-            return false;
-        }
-        return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
-    }
-
-    /**
-     * Checks if an app has been idle for a while and filters out apps that are excluded.
-     * It returns false if the current system state allows all apps to be considered active.
-     * This happens if the device is plugged in or temporarily allowed to make exceptions.
-     * Called by interface impls.
-     */
-    private boolean isAppIdleFiltered(String packageName, int appId, int userId,
-            long elapsedRealtime) {
-        if (packageName == null) return false;
-        // If not enabled at all, of course nobody is ever idle.
-        if (!mAppIdleEnabled) {
-            return false;
-        }
-        if (appId < Process.FIRST_APPLICATION_UID) {
-            // System uids never go idle.
-            return false;
-        }
-        if (packageName.equals("android")) {
-            // Nor does the framework (which should be redundant with the above, but for MR1 we will
-            // retain this for safety).
-            return false;
-        }
-        if (mSystemServicesReady) {
-            try {
-                // We allow all whitelisted apps, including those that don't want to be whitelisted
-                // for idle mode, because app idle (aka app standby) is really not as big an issue
-                // for controlling who participates vs. doze mode.
-                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
-                    return false;
-                }
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-
-            if (isActiveDeviceAdmin(packageName, userId)) {
-                return false;
-            }
-
-            if (isActiveNetworkScorer(packageName)) {
-                return false;
-            }
-
-            if (mAppWidgetManager != null
-                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
-                return false;
-            }
-
-            if (isDeviceProvisioningPackage(packageName)) {
-                return false;
-            }
-        }
-
-        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
-            return false;
-        }
-
-        // Check this last, as it is the most expensive check
-        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
-        if (isCarrierApp(packageName)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    int[] getIdleUidsForUser(int userId) {
-        if (!mAppIdleEnabled) {
-            return new int[0];
-        }
-
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-
-        List<ApplicationInfo> apps;
-        try {
-            ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
-                    .getInstalledApplications(/* flags= */ 0, userId);
-            if (slice == null) {
-                return new int[0];
-            }
-            apps = slice.getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
-        // associated with that uid, upper 16 bits is the number of those apps that is idle.
-        SparseIntArray uidStates = new SparseIntArray();
-
-        // Now resolve all app state.  Iterating over all apps, keeping track of how many
-        // we find for each uid and how many of those are idle.
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            ApplicationInfo ai = apps.get(i);
-
-            // Check whether this app is idle.
-            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
-                    userId, elapsedRealtime);
-
-            int index = uidStates.indexOfKey(ai.uid);
-            if (index < 0) {
-                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
-            } else {
-                int value = uidStates.valueAt(index);
-                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
-        }
-        int numIdle = 0;
-        for (int i = uidStates.size() - 1; i >= 0; i--) {
-            int value = uidStates.valueAt(i);
-            if ((value&0x7fff) == (value>>16)) {
-                numIdle++;
-            }
-        }
-
-        int[] res = new int[numIdle];
-        numIdle = 0;
-        for (int i = uidStates.size() - 1; i >= 0; i--) {
-            int value = uidStates.valueAt(i);
-            if ((value&0x7fff) == (value>>16)) {
-                res[numIdle] = uidStates.keyAt(i);
-                numIdle++;
-            }
-        }
-
-        return res;
-    }
-
-    void setAppIdleAsync(String packageName, boolean idle, int userId) {
-        if (packageName == null) return;
-
-        mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
-                .sendToTarget();
-    }
-
-    private boolean isActiveDeviceAdmin(String packageName, int userId) {
-        DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
-        if (dpm == null) return false;
-        return dpm.packageHasActiveAdmins(packageName, userId);
-    }
-
-    /**
-     * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
-     * returns {@code false}.
-     */
-    private boolean isDeviceProvisioningPackage(String packageName) {
-        String deviceProvisioningPackage = getContext().getResources().getString(
-                com.android.internal.R.string.config_deviceProvisioningPackage);
-        return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
-    }
-
-    private boolean isCarrierApp(String packageName) {
-        synchronized (mAppIdleLock) {
-            if (!mHaveCarrierPrivilegedApps) {
-                fetchCarrierPrivilegedAppsLA();
-            }
-            if (mCarrierPrivilegedApps != null) {
-                return mCarrierPrivilegedApps.contains(packageName);
-            }
-            return false;
-        }
-    }
-
-    void clearCarrierPrivilegedApps() {
-        if (DEBUG) {
-            Slog.i(TAG, "Clearing carrier privileged apps list");
-        }
-        synchronized (mAppIdleLock) {
-            mHaveCarrierPrivilegedApps = false;
-            mCarrierPrivilegedApps = null; // Need to be refetched.
-        }
-    }
-
-    @GuardedBy("mAppIdleLock")
-    private void fetchCarrierPrivilegedAppsLA() {
-        TelephonyManager telephonyManager =
-                getContext().getSystemService(TelephonyManager.class);
-        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
-        mHaveCarrierPrivilegedApps = true;
-        if (DEBUG) {
-            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
-        }
-    }
-
-    private boolean isActiveNetworkScorer(String packageName) {
-        NetworkScoreManager nsm = (NetworkScoreManager) getContext().getSystemService(
-                Context.NETWORK_SCORE_SERVICE);
-        return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
-    }
-
-    void informListeners(String packageName, int userId, boolean isIdle) {
-        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onAppIdleStateChanged(packageName, userId, isIdle);
-        }
-    }
-
-    void informParoleStateChanged() {
-        final boolean paroled = isParoledOrCharging();
-        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onParoleStateChanged(paroled);
-        }
-    }
-
     private static boolean validRange(long currentTime, long beginTime, long endTime) {
         return beginTime <= currentTime && beginTime < endTime;
     }
@@ -1143,15 +456,10 @@
         for (int i = 0; i < userCount; i++) {
             UserUsageStatsService service = mUserState.valueAt(i);
             service.persistActiveStats();
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.writeAppIdleTimes(mUserState.keyAt(i));
-            }
+            mAppStandby.flushToDisk(mUserState.keyAt(i));
         }
-        // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
-        // considered not-idle, which is the safest outcome in such an event.
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.writeAppIdleDurations();
-        }
+        mAppStandby.flushDurationsToDisk();
+
         mHandler.removeMessages(MSG_FLUSH_TO_DISK);
     }
 
@@ -1166,7 +474,8 @@
 
             final int userCount = mUserState.size();
             for (int i = 0; i < userCount; i++) {
-                idpw.printPair("user", mUserState.keyAt(i));
+                int userId = mUserState.keyAt(i);
+                idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
                 if (argSet.contains("--checkin")) {
@@ -1176,57 +485,19 @@
                     idpw.println();
                     if (args.length > 0) {
                         if ("history".equals(args[0])) {
-                            synchronized (mAppIdleLock) {
-                                mAppIdleHistory.dumpHistory(idpw, mUserState.keyAt(i));
-                            }
+                            mAppStandby.dumpHistory(idpw, userId);
                         } else if ("flush".equals(args[0])) {
-                            UsageStatsService.this.flushToDiskLocked();
+                            flushToDiskLocked();
                             pw.println("Flushed stats to disk");
                         }
                     }
                 }
-                synchronized (mAppIdleLock) {
-                    mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
-                }
+                mAppStandby.dumpUser(idpw, userId);
                 idpw.decreaseIndent();
             }
 
             pw.println();
-            synchronized (mAppIdleLock) {
-                pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
-                        + "): " + mCarrierPrivilegedApps);
-            }
-
-            pw.println();
-            pw.println("Settings:");
-
-            pw.print("  mAppIdleDurationMillis=");
-            TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleWallclockThresholdMillis=");
-            TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
-            pw.println();
-
-            pw.print("  mCheckIdleIntervalMillis=");
-            TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleParoleIntervalMillis=");
-            TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleParoleDurationMillis=");
-            TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
-            pw.println();
-
-            pw.println();
-            pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
-            pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
-            pw.print(" mCharging="); pw.print(mCharging);
-            pw.print(" mLastAppIdleParoledTime=");
-            TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
-            pw.println();
+            mAppStandby.dumpState(args, pw);
         }
     }
 
@@ -1250,50 +521,6 @@
                     onUserRemoved(msg.arg1);
                     break;
 
-                case MSG_INFORM_LISTENERS:
-                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
-                    break;
-
-                case MSG_FORCE_IDLE_STATE:
-                    forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
-                    break;
-
-                case MSG_CHECK_IDLE_STATES:
-                    if (checkIdleStates(msg.arg1)) {
-                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
-                                mCheckIdleIntervalMillis);
-                    }
-                    break;
-
-                case MSG_ONE_TIME_CHECK_IDLE_STATES:
-                    mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
-                    checkIdleStates(UserHandle.USER_ALL);
-                    break;
-
-                case MSG_CHECK_PAROLE_TIMEOUT:
-                    checkParoleTimeout();
-                    break;
-
-                case MSG_PAROLE_END_TIMEOUT:
-                    if (DEBUG) Slog.d(TAG, "Ending parole");
-                    setAppIdleParoled(false);
-                    break;
-
-                case MSG_REPORT_CONTENT_PROVIDER_USAGE:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    reportContentProviderUsage((String) args.arg1, // authority name
-                            (String) args.arg2, // package name
-                            (int) args.arg3); // userId
-                    args.recycle();
-                    break;
-
-                case MSG_PAROLE_STATE_CHANGED:
-                    if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
-                            + ", Charging state:" + mCharging);
-                    informParoleStateChanged();
-                    break;
-
                 default:
                     super.handleMessage(msg);
                     break;
@@ -1301,72 +528,6 @@
         }
     }
 
-    /**
-     * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
-     */
-    private class SettingsObserver extends ContentObserver {
-        /**
-         * This flag has been used to disable app idle on older builds with bug b/26355386.
-         */
-        @Deprecated
-        private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
-
-        private static final String KEY_IDLE_DURATION = "idle_duration2";
-        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
-        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
-        private static final String KEY_PAROLE_DURATION = "parole_duration";
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-
-        SettingsObserver(Handler handler) {
-            super(handler);
-        }
-
-        void registerObserver() {
-            getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            updateSettings();
-            postOneTimeCheckIdleStates();
-        }
-
-        void updateSettings() {
-            synchronized (mAppIdleLock) {
-                // Look at global settings for this.
-                // TODO: Maybe apply different thresholds for different users.
-                try {
-                    mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
-                            Settings.Global.APP_IDLE_CONSTANTS));
-                } catch (IllegalArgumentException e) {
-                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
-                    // fallthrough, mParser is empty and all defaults will be returned.
-                }
-
-                // Default: 12 hours of screen-on time sans dream-time
-                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
-                       COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
-
-                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
-                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
-
-                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
-                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
-
-                // Default: 24 hours between paroles
-                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
-                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
-
-                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
-                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
-                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
-                        mAppIdleScreenThresholdMillis);
-            }
-        }
-    }
-
     private final class BinderService extends IUsageStatsManager.Stub {
 
         private boolean hasPermission(String callingPackage) {
@@ -1462,7 +623,8 @@
                     Binder.getCallingUid(), userId);
             final long token = Binder.clearCallingIdentity();
             try {
-                return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId,
+                return mAppStandby.isAppIdleFilteredOrParoled(
+                        packageName, userId,
                         SystemClock.elapsedRealtime(), obfuscateInstantApps);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1483,9 +645,9 @@
                     "No permission to change app idle state");
             final long token = Binder.clearCallingIdentity();
             try {
-                final int appId = getAppId(packageName);
+                final int appId = mAppStandby.getAppId(packageName);
                 if (appId < 0) return;
-                UsageStatsService.this.setAppIdleAsync(packageName, idle, userId);
+                mAppStandby.setAppIdleAsync(packageName, idle, userId);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1509,7 +671,7 @@
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.BIND_CARRIER_SERVICES,
                     "onCarrierPrivilegedAppsChanged can only be called by privileged apps.");
-            UsageStatsService.this.clearCarrierPrivilegedApps();
+            mAppStandby.clearCarrierPrivilegedApps();
         }
 
         @Override
@@ -1624,28 +786,23 @@
 
         @Override
         public void reportContentProviderUsage(String name, String packageName, int userId) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = name;
-            args.arg2 = packageName;
-            args.arg3 = userId;
-            mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
-                    .sendToTarget();
+            mAppStandby.postReportContentProviderUsage(name, packageName, userId);
         }
 
         @Override
         public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
-            return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId,
-                    SystemClock.elapsedRealtime());
+            return mAppStandby.isAppIdleFiltered(packageName, uidForAppId,
+                    userId, SystemClock.elapsedRealtime());
         }
 
         @Override
         public int[] getIdleUidsForUser(int userId) {
-            return UsageStatsService.this.getIdleUidsForUser(userId);
+            return mAppStandby.getIdleUidsForUser(userId);
         }
 
         @Override
         public boolean isAppIdleParoleOn() {
-            return isParoledOrCharging();
+            return mAppStandby.isParoledOrCharging();
         }
 
         @Override
@@ -1658,20 +815,20 @@
 
         @Override
         public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
-            UsageStatsService.this.addListener(listener);
+            mAppStandby.addListener(listener);
             listener.onParoleStateChanged(isAppIdleParoleOn());
         }
 
         @Override
         public void removeAppIdleStateChangeListener(
                 AppIdleStateChangeListener listener) {
-            UsageStatsService.this.removeListener(listener);
+            mAppStandby.removeListener(listener);
         }
 
         @Override
         public byte[] getBackupPayload(int user, String key) {
             // Check to ensure that only user 0's data is b/r for now
-            synchronized (UsageStatsService.this.mLock) {
+            synchronized (mLock) {
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats =
                             getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
@@ -1684,7 +841,7 @@
 
         @Override
         public void applyRestoredPayload(int user, String key, byte[] payload) {
-            synchronized (UsageStatsService.this.mLock) {
+            synchronized (mLock) {
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats =
                             getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index 68c1d5f..acc27be 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -314,7 +314,11 @@
             return null;
         }
 
-        mDevicesParser.scan();
+        if (!mDevicesParser.scan()) {
+            Slog.e(TAG, "Error parsing ALSA devices file.");
+            return null;
+        }
+
         int device = mDevicesParser.getDefaultDeviceNum(card);
 
         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
diff --git a/com/android/server/utils/ManagedApplicationService.java b/com/android/server/utils/ManagedApplicationService.java
index 0f251fd..c555388 100644
--- a/com/android/server/utils/ManagedApplicationService.java
+++ b/com/android/server/utils/ManagedApplicationService.java
@@ -16,19 +16,24 @@
 package com.android.server.utils;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.IInterface;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Slog;
 
+import java.text.SimpleDateFormat;
 import java.util.Objects;
+import java.util.Date;
 
 /**
  * Manages the lifecycle of an application-provided service bound from system server.
@@ -38,39 +43,126 @@
 public class ManagedApplicationService {
     private final String TAG = getClass().getSimpleName();
 
+    /**
+     * Attempt to reconnect service forever if an onBindingDied or onServiceDisconnected event
+     * is received.
+     */
+    public static final int RETRY_FOREVER = 1;
+
+    /**
+     * Never attempt to reconnect the service - a single onBindingDied or onServiceDisconnected
+     * event will cause this to fully unbind the service and never attempt to reconnect.
+     */
+    public static final int RETRY_NEVER = 2;
+
+    /**
+     * Attempt to reconnect the service until the maximum number of retries is reached, then stop.
+     *
+     * The first retry will occur MIN_RETRY_DURATION_MS after the disconnection, and each
+     * subsequent retry will occur after 2x the duration used for the previous retry up to the
+     * MAX_RETRY_DURATION_MS duration.
+     *
+     * In this case, retries mean a full unbindService/bindService pair to handle cases when the
+     * usual service re-connection logic in ActiveServices has very high backoff times or when the
+     * serviceconnection has fully died due to a package update or similar.
+     */
+    public static final int RETRY_BEST_EFFORT = 3;
+
+    // Maximum number of retries before giving up (for RETRY_BEST_EFFORT).
+    private static final int MAX_RETRY_COUNT = 4;
+    // Max time between retry attempts.
+    private static final long MAX_RETRY_DURATION_MS = 16000;
+    // Min time between retry attempts.
+    private static final long MIN_RETRY_DURATION_MS = 2000;
+    // Time since the last retry attempt after which to clear the retry attempt counter.
+    private static final long RETRY_RESET_TIME_MS = MAX_RETRY_DURATION_MS * 4;
+
     private final Context mContext;
     private final int mUserId;
     private final ComponentName mComponent;
     private final int mClientLabel;
     private final String mSettingsAction;
     private final BinderChecker mChecker;
-
-    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
-        @Override
-        public void binderDied() {
-            synchronized (mLock) {
-                mBoundInterface = null;
-            }
-        }
-    };
+    private final boolean mIsImportant;
+    private final int mRetryType;
+    private final Handler mHandler;
+    private final Runnable mRetryRunnable = this::doRetry;
+    private final EventCallback mEventCb;
 
     private final Object mLock = new Object();
 
     // State protected by mLock
-    private ServiceConnection mPendingConnection;
     private ServiceConnection mConnection;
     private IInterface mBoundInterface;
     private PendingEvent mPendingEvent;
+    private int mRetryCount;
+    private long mLastRetryTimeMs;
+    private long mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+    private boolean mRetrying;
+
+    public static interface LogFormattable {
+       String toLogString(SimpleDateFormat dateFormat);
+    }
+
+    /**
+     * Lifecycle event of this managed service.
+     */
+    public static class LogEvent implements LogFormattable {
+        public static final int EVENT_CONNECTED = 1;
+        public static final int EVENT_DISCONNECTED = 2;
+        public static final int EVENT_BINDING_DIED = 3;
+        public static final int EVENT_STOPPED_PERMANENTLY = 4;
+
+        // Time of the events in "current time ms" timebase.
+        public final long timestamp;
+        // Name of the component for this system service.
+        public final ComponentName component;
+        // ID of the event that occurred.
+        public final int event;
+
+        public LogEvent(long timestamp, ComponentName component, int event) {
+            this.timestamp = timestamp;
+            this.component = component;
+            this.event = event;
+        }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            return dateFormat.format(new Date(timestamp)) + "   " + eventToString(event)
+                    + " Managed Service: "
+                    + ((component == null) ? "None" : component.flattenToString());
+        }
+
+        public static String eventToString(int event) {
+            switch (event) {
+                case EVENT_CONNECTED:
+                    return "Connected";
+                case EVENT_DISCONNECTED:
+                    return "Disconnected";
+                case EVENT_BINDING_DIED:
+                    return "Binding Died For";
+                case EVENT_STOPPED_PERMANENTLY:
+                    return "Permanently Stopped";
+                default:
+                    return "Unknown Event Occurred";
+            }
+        }
+    }
 
     private ManagedApplicationService(final Context context, final ComponentName component,
             final int userId, int clientLabel, String settingsAction,
-            BinderChecker binderChecker) {
+            BinderChecker binderChecker, boolean isImportant, int retryType, Handler handler,
+            EventCallback eventCallback) {
         mContext = context;
         mComponent = component;
         mUserId = userId;
         mClientLabel = clientLabel;
         mSettingsAction = settingsAction;
         mChecker = binderChecker;
+        mIsImportant = isImportant;
+        mRetryType = retryType;
+        mHandler = handler;
+        mEventCb = eventCallback;
     }
 
     /**
@@ -85,7 +177,17 @@
      * Implement to call IInterface methods after service is connected.
      */
     public interface PendingEvent {
-         void runEvent(IInterface service) throws RemoteException;
+        void runEvent(IInterface service) throws RemoteException;
+    }
+
+    /**
+     * Implement to be notified about any problems with remote service.
+     */
+    public interface EventCallback {
+        /**
+         * Called when an sevice lifecycle event occurs.
+         */
+        void onServiceEvent(LogEvent event);
     }
 
     /**
@@ -95,19 +197,28 @@
      * @param component the {@link ComponentName} of the application service to bind.
      * @param userId the user ID of user to bind the application service as.
      * @param clientLabel the resource ID of a label displayed to the user indicating the
-     *      binding service.
+     *      binding service, or 0 if none is desired.
      * @param settingsAction an action that can be used to open the Settings UI to enable/disable
-     *      binding to these services.
-     * @param binderChecker an interface used to validate the returned binder object.
+     *      binding to these services, or null if none is desired.
+     * @param binderChecker an interface used to validate the returned binder object, or null if
+     *      this interface is unchecked.
+     * @param isImportant bind the user service with BIND_IMPORTANT.
+     * @param retryType reconnect behavior to have when bound service is disconnected.
+     * @param handler the Handler to use for retries and delivering EventCallbacks.
+     * @param eventCallback a callback used to deliver disconnection events, or null if you
+     *      don't care.
      * @return a ManagedApplicationService instance.
      */
     public static ManagedApplicationService build(@NonNull final Context context,
-        @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
-        @NonNull String settingsAction, @NonNull BinderChecker binderChecker) {
+            @NonNull final ComponentName component, final int userId, int clientLabel,
+            @Nullable String settingsAction, @Nullable BinderChecker binderChecker,
+            boolean isImportant, int retryType, @NonNull Handler handler,
+            @Nullable EventCallback eventCallback) {
         return new ManagedApplicationService(context, component, userId, clientLabel,
-            settingsAction, binderChecker);
+            settingsAction, binderChecker, isImportant, retryType, handler, eventCallback);
     }
 
+
     /**
      * @return the user ID of the user that owns the bound service.
      */
@@ -138,13 +249,12 @@
         return true;
     }
 
-
-  /**
-   * Send an event to run as soon as the binder interface is available.
-   *
-   * @param event a {@link PendingEvent} to send.
-   */
-  public void sendEvent(@NonNull PendingEvent event) {
+    /**
+     * Send an event to run as soon as the binder interface is available.
+     *
+     * @param event a {@link PendingEvent} to send.
+     */
+    public void sendEvent(@NonNull PendingEvent event) {
         IInterface iface;
         synchronized (mLock) {
             iface = mBoundInterface;
@@ -167,15 +277,13 @@
      */
     public void disconnect() {
         synchronized (mLock) {
-            // Wipe out pending connections
-            mPendingConnection = null;
-
             // Unbind existing connection, if it exists
-            if (mConnection != null) {
-                mContext.unbindService(mConnection);
-                mConnection = null;
+            if (mConnection == null) {
+                return;
             }
 
+            mContext.unbindService(mConnection);
+            mConnection = null;
             mBoundInterface = null;
         }
     }
@@ -185,48 +293,70 @@
      */
     public void connect() {
         synchronized (mLock) {
-            if (mConnection != null || mPendingConnection != null) {
+            if (mConnection != null) {
                 // We're already connected or are trying to connect
                 return;
             }
 
-            final PendingIntent pendingIntent = PendingIntent.getActivity(
-                    mContext, 0, new Intent(mSettingsAction), 0);
-            final Intent intent = new Intent().setComponent(mComponent).
-                    putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
-                    putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+            Intent intent  = new Intent().setComponent(mComponent);
+            if (mClientLabel != 0) {
+                intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel);
+            }
+            if (mSettingsAction != null) {
+                intent.putExtra(Intent.EXTRA_CLIENT_INTENT,
+                        PendingIntent.getActivity(mContext, 0, new Intent(mSettingsAction), 0));
+            }
 
-            final ServiceConnection serviceConnection = new ServiceConnection() {
+            mConnection = new ServiceConnection() {
+                @Override
+                public void onBindingDied(ComponentName componentName) {
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.w(TAG, "Service binding died: " + componentName);
+                    synchronized (mLock) {
+                        if (mConnection != this) {
+                            return;
+                        }
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_BINDING_DIED));
+                        });
+
+                        mBoundInterface = null;
+                        startRetriesLocked();
+                    }
+                }
+
                 @Override
                 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.i(TAG, "Service connected: " + componentName);
                     IInterface iface = null;
                     PendingEvent pendingEvent = null;
                     synchronized (mLock) {
-                        if (mPendingConnection == this) {
-                            // No longer pending, remove from pending connection
-                            mPendingConnection = null;
-                            mConnection = this;
-                        } else {
-                            // Service connection wasn't pending, must have been disconnected
-                            mContext.unbindService(this);
+                        if (mConnection != this) {
+                            // Must've been unbound.
                             return;
                         }
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_CONNECTED));
+                        });
 
-                        try {
-                            iBinder.linkToDeath(mDeathRecipient, 0);
+                        stopRetriesLocked();
+
+                        mBoundInterface = null;
+                        if (mChecker != null) {
                             mBoundInterface = mChecker.asInterface(iBinder);
                             if (!mChecker.checkType(mBoundInterface)) {
-                                // Received an invalid binder, disconnect
-                                mContext.unbindService(this);
+                                // Received an invalid binder, disconnect.
                                 mBoundInterface = null;
+                                Slog.w(TAG, "Invalid binder from " + componentName);
+                                startRetriesLocked();
+                                return;
                             }
                             iface = mBoundInterface;
                             pendingEvent = mPendingEvent;
                             mPendingEvent = null;
-                        } catch (RemoteException e) {
-                            // DOA
-                            Slog.w(TAG, "Unable to bind service: " + intent, e);
-                            mBoundInterface = null;
                         }
                     }
                     if (iface != null && pendingEvent != null) {
@@ -234,28 +364,44 @@
                             pendingEvent.runEvent(iface);
                         } catch (RuntimeException | RemoteException ex) {
                             Slog.e(TAG, "Received exception from user service: ", ex);
+                            startRetriesLocked();
                         }
                     }
                 }
 
                 @Override
                 public void onServiceDisconnected(ComponentName componentName) {
-                    Slog.w(TAG, "Service disconnected: " + intent);
-                    mConnection = null;
-                    mBoundInterface = null;
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.w(TAG, "Service disconnected: " + componentName);
+                    synchronized (mLock) {
+                        if (mConnection != this) {
+                            return;
+                        }
+
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_DISCONNECTED));
+                        });
+
+                        mBoundInterface = null;
+                        startRetriesLocked();
+                    }
                 }
             };
 
-            mPendingConnection = serviceConnection;
-
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+            if (mIsImportant) {
+                flags |= Context.BIND_IMPORTANT;
+            }
             try {
-                if (!mContext.bindServiceAsUser(intent, serviceConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                if (!mContext.bindServiceAsUser(intent, mConnection, flags,
                         new UserHandle(mUserId))) {
                     Slog.w(TAG, "Unable to bind service: " + intent);
+                    startRetriesLocked();
                 }
             } catch (SecurityException e) {
                 Slog.w(TAG, "Unable to bind service: " + intent, e);
+                startRetriesLocked();
             }
         }
     }
@@ -263,4 +409,81 @@
     private boolean matches(final ComponentName component, final int userId) {
         return Objects.equals(mComponent, component) && mUserId == userId;
     }
+
+    private void startRetriesLocked() {
+        if (checkAndDeliverServiceDiedCbLocked()) {
+            // If we delivered the service callback, disconnect and stop retrying.
+            disconnect();
+            return;
+        }
+
+        if (mRetrying) {
+            // Retry already queued, don't queue a new one.
+            return;
+        }
+        mRetrying = true;
+        queueRetryLocked();
+    }
+
+    private void stopRetriesLocked() {
+        mRetrying = false;
+        mHandler.removeCallbacks(mRetryRunnable);
+    }
+
+    private void queueRetryLocked() {
+        long now = SystemClock.uptimeMillis();
+        if ((now - mLastRetryTimeMs) > RETRY_RESET_TIME_MS) {
+            // It's been longer than the reset time since we last had to retry.  Re-initialize.
+            mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+            mRetryCount = 0;
+        }
+        mLastRetryTimeMs = now;
+        mHandler.postDelayed(mRetryRunnable, mNextRetryDurationMs);
+        mNextRetryDurationMs = Math.min(2 * mNextRetryDurationMs, MAX_RETRY_DURATION_MS);
+        mRetryCount++;
+    }
+
+    private boolean checkAndDeliverServiceDiedCbLocked() {
+
+       if (mRetryType == RETRY_NEVER || (mRetryType == RETRY_BEST_EFFORT
+                && mRetryCount >= MAX_RETRY_COUNT)) {
+            // If we never retry, or we've exhausted our retries, post the onServiceDied callback.
+            Slog.e(TAG, "Service " + mComponent + " has died too much, not retrying.");
+            if (mEventCb != null) {
+                final long timestamp = System.currentTimeMillis();
+                mHandler.post(() -> {
+                  mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                        LogEvent.EVENT_STOPPED_PERMANENTLY));
+                });
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void doRetry() {
+        synchronized (mLock) {
+            if (mConnection == null) {
+                // We disconnected for good.  Don't attempt to retry.
+                return;
+            }
+            if (!mRetrying) {
+                // We successfully connected.  Don't attempt to retry.
+                return;
+            }
+            Slog.i(TAG, "Attempting to reconnect " + mComponent + "...");
+            // While frameworks may restart the remote Service if we stay bound, we have little
+            // control of the backoff timing for reconnecting the service.  In the event of a
+            // process crash, the backoff time can be very large (1-30 min), which is not
+            // acceptable for the types of services this is used for.  Instead force an unbind/bind
+            // sequence to cause a more immediate retry.
+            disconnect();
+            if (checkAndDeliverServiceDiedCbLocked()) {
+                // No more retries.
+                return;
+            }
+            queueRetryLocked();
+            connect();
+        }
+    }
 }
diff --git a/com/android/server/utils/PriorityDump.java b/com/android/server/utils/PriorityDump.java
index c05cc3f..054f156 100644
--- a/com/android/server/utils/PriorityDump.java
+++ b/com/android/server/utils/PriorityDump.java
@@ -59,10 +59,10 @@
     Donuts in the box: 1
     Nuclear reactor status: DANGER - MELTDOWN IMMINENT
 
-    $ adb shell dumpsys snpp --dump_priority CRITICAL
+    $ adb shell dumpsys snpp --dump-priority CRITICAL
     Donuts in the box: 1
 
-    $ adb shell dumpsys snpp --dump_priority NORMAL
+    $ adb shell dumpsys snpp --dump-priority NORMAL
     Nuclear reactor status: DANGER - MELTDOWN IMMINENT
 
  * </code></pre>
@@ -84,7 +84,7 @@
  */
 public final class PriorityDump {
 
-    public static final String PRIORITY_ARG = "--dump_priority";
+    public static final String PRIORITY_ARG = "--dump-priority";
 
     private PriorityDump() {
         throw new UnsupportedOperationException();
@@ -92,12 +92,12 @@
 
     /**
      * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is
-     * {@code --dump_priority}, stripping the priority and its type.
+     * {@code --dump-priority}, stripping the priority and its type.
      * <p>
-     * For example, if called as {@code --dump_priority HIGH arg1 arg2 arg3}, it will call
+     * For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call
      * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code>
      * <p>
-     * If the {@code --dump_priority} is not set, it calls
+     * If the {@code --dump-priority} is not set, it calls
      * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole
      * {@code args} instead.
      */
@@ -124,7 +124,7 @@
     }
 
     /**
-     * Gets an array without the {@code --dump_priority PRIORITY} prefix.
+     * Gets an array without the {@code --dump-priority PRIORITY} prefix.
      */
     private static String[] getStrippedArgs(String[] args) {
         final String[] stripped = new String[args.length - 2];
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 3788cf3..b040a63 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION;
 import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
 import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackId;
@@ -222,8 +223,8 @@
             }
             intent = new Intent(intent);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            ActivityOptions options = ActivityOptions.makeBasic();
-            options.setLaunchStackId(StackId.ASSISTANT_STACK_ID);
+            final ActivityOptions options = ActivityOptions.makeBasic();
+            options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT);
             return mAm.startAssistantActivity(mComponent.getPackageName(), callingPid, callingUid,
                     intent, resolvedType, options.toBundle(), mUser);
         } catch (RemoteException e) {
diff --git a/com/android/server/vr/Vr2dDisplay.java b/com/android/server/vr/Vr2dDisplay.java
index 8f50a39..95d03d4 100644
--- a/com/android/server/vr/Vr2dDisplay.java
+++ b/com/android/server/vr/Vr2dDisplay.java
@@ -294,6 +294,9 @@
 
             int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
             mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
                     DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
                     null /* surface */, flags, null /* callback */, null /* handler */,
diff --git a/com/android/server/vr/VrManagerInternal.java b/com/android/server/vr/VrManagerInternal.java
index bdd9de0..7b1e12e 100644
--- a/com/android/server/vr/VrManagerInternal.java
+++ b/com/android/server/vr/VrManagerInternal.java
@@ -74,6 +74,13 @@
     public abstract void onScreenStateChanged(boolean isScreenOn);
 
     /**
+     * Set whether the keyguard is currently active/showing.
+     *
+     * @param isShowing is {@code true} if the keyguard is active/showing.
+     */
+    public abstract void onKeyguardStateChanged(boolean isShowing);
+
+    /**
      * Return NO_ERROR if the given package is installed on the device and enabled as a
      * VrListenerService for the given current user, or a negative error code indicating a failure.
      *
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index 1f0b2f0..e7e4efc 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -66,6 +66,8 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.utils.ManagedApplicationService.PendingEvent;
+import com.android.server.utils.ManagedApplicationService.LogEvent;
+import com.android.server.utils.ManagedApplicationService.LogFormattable;
 import com.android.server.vr.EnabledComponentsObserver.EnabledComponentChangeListener;
 import com.android.server.utils.ManagedApplicationService;
 import com.android.server.utils.ManagedApplicationService.BinderChecker;
@@ -108,16 +110,18 @@
     static final boolean DBG = false;
 
     private static final int PENDING_STATE_DELAY_MS = 300;
-    private static final int EVENT_LOG_SIZE = 32;
+    private static final int EVENT_LOG_SIZE = 64;
     private static final int INVALID_APPOPS_MODE = -1;
     /** Null set of sleep sleep flags. */
     private static final int FLAG_NONE = 0;
     /** Flag set when the device is not sleeping. */
-    private static final int FLAG_AWAKE = 1;
+    private static final int FLAG_AWAKE = 1 << 0;
     /** Flag set when the screen has been turned on. */
-    private static final int FLAG_SCREEN_ON = 2;
+    private static final int FLAG_SCREEN_ON = 1 << 1;
+    /** Flag set when the keyguard is not active. */
+    private static final int FLAG_KEYGUARD_UNLOCKED = 1 << 2;
     /** Flag indicating that all system sleep flags have been set.*/
-    private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON;
+    private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON | FLAG_KEYGUARD_UNLOCKED;
 
     private static native void initializeNative();
     private static native void setVrModeNative(boolean enabled);
@@ -134,6 +138,7 @@
     private int mVrAppProcessId;
     private EnabledComponentsObserver mComponentObserver;
     private ManagedApplicationService mCurrentVrService;
+    private ManagedApplicationService mCurrentVrCompositorService;
     private ComponentName mDefaultVrService;
     private Context mContext;
     private ComponentName mCurrentVrModeComponent;
@@ -147,19 +152,45 @@
     private int mPreviousCoarseLocationMode = INVALID_APPOPS_MODE;
     private int mPreviousManageOverlayMode = INVALID_APPOPS_MODE;
     private VrState mPendingState;
-    private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
+    private boolean mLogLimitHit;
+    private final ArrayDeque<LogFormattable> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
     private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
     private INotificationManager mNotificationManager;
     /** Tracks the state of the screen and keyguard UI.*/
-    private int mSystemSleepFlags = FLAG_AWAKE;
+    private int mSystemSleepFlags = FLAG_AWAKE | FLAG_KEYGUARD_UNLOCKED;
     /**
      * Set when ACTION_USER_UNLOCKED is fired. We shouldn't try to bind to the
-     * vr service before then.
+     * vr service before then. This gets set only once the first time the user unlocks the device
+     * and stays true thereafter.
      */
     private boolean mUserUnlocked;
     private Vr2dDisplay mVr2dDisplay;
     private boolean mBootsToVr;
 
+    // Handles events from the managed services (e.g. VrListenerService and any bound VR compositor
+    // service).
+    private final ManagedApplicationService.EventCallback mEventCallback
+                = new ManagedApplicationService.EventCallback() {
+        @Override
+        public void onServiceEvent(LogEvent event) {
+            logEvent(event);
+
+            ComponentName component = null;
+            synchronized (mLock) {
+                component = ((mCurrentVrService == null) ? null : mCurrentVrService.getComponent());
+            }
+
+            // If not on an AIO device and we permanently stopped trying to connect to the
+            // VrListenerService (or don't have one bound), leave persistent VR mode and VR mode.
+            if (!mBootsToVr && event.event == LogEvent.EVENT_STOPPED_PERMANENTLY &&
+                    (component == null || component.equals(event.component))) {
+                Slog.e(TAG, "VrListenerSevice has died permanently, leaving system VR mode.");
+                // We're not a native VR device.  Leave VR + persistent mode.
+                setPersistentVrModeEnabled(false);
+            }
+        }
+    };
+
     private static final int MSG_VR_STATE_CHANGE = 0;
     private static final int MSG_PENDING_VR_STATE_CHANGE = 1;
     private static final int MSG_PERSISTENT_VR_MODE_STATE_CHANGE = 2;
@@ -180,7 +211,6 @@
                 if (mBootsToVr) {
                     setPersistentVrModeEnabled(true);
                 }
-                consumeAndApplyPendingStateLocked();
                 if (mBootsToVr && !mVrModeEnabled) {
                   setVrMode(true, mDefaultVrService, 0, -1, null);
                 }
@@ -202,29 +232,40 @@
     }
 
     private void setSleepState(boolean isAsleep) {
-        synchronized(mLock) {
-
-            if (!isAsleep) {
-                mSystemSleepFlags |= FLAG_AWAKE;
-            } else {
-                mSystemSleepFlags &= ~FLAG_AWAKE;
-            }
-
-            updateVrModeAllowedLocked();
-        }
+        setSystemState(FLAG_AWAKE, !isAsleep);
     }
 
     private void setScreenOn(boolean isScreenOn) {
+        setSystemState(FLAG_SCREEN_ON, isScreenOn);
+    }
+
+    private void setKeyguardShowing(boolean isShowing) {
+        setSystemState(FLAG_KEYGUARD_UNLOCKED, !isShowing);
+    }
+
+    private void setSystemState(int flags, boolean isOn) {
         synchronized(mLock) {
-            if (isScreenOn) {
-                mSystemSleepFlags |= FLAG_SCREEN_ON;
+            int oldState = mSystemSleepFlags;
+            if (isOn) {
+                mSystemSleepFlags |= flags;
             } else {
-                mSystemSleepFlags &= ~FLAG_SCREEN_ON;
+                mSystemSleepFlags &= ~flags;
             }
-            updateVrModeAllowedLocked();
+            if (oldState != mSystemSleepFlags) {
+                if (DBG) Slog.d(TAG, "System state: " + getStateAsString());
+                updateVrModeAllowedLocked();
+            }
         }
     }
 
+    private String getStateAsString() {
+        return new StringBuilder()
+                .append((mSystemSleepFlags & FLAG_AWAKE) != 0 ? "awake, " : "")
+                .append((mSystemSleepFlags & FLAG_SCREEN_ON) != 0 ? "screen_on, " : "")
+                .append((mSystemSleepFlags & FLAG_KEYGUARD_UNLOCKED) != 0 ? "keyguard_off" : "")
+                .toString();
+    }
+
     private void setUserUnlocked() {
         synchronized(mLock) {
             mUserUnlocked = true;
@@ -276,7 +317,24 @@
         }
     };
 
-    private static class VrState {
+    // Event used to log when settings are changed for dumpsys logs.
+    private static class SettingEvent implements LogFormattable {
+        public final long timestamp;
+        public final String what;
+
+        SettingEvent(String what) {
+            this.timestamp = System.currentTimeMillis();
+            this.what = what;
+        }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            return dateFormat.format(new Date(timestamp)) + "   " + what;
+        }
+    }
+
+    // Event used to track changes of the primary on-screen VR activity.
+    private static class VrState implements LogFormattable {
         final boolean enabled;
         final boolean running2dInVr;
         final int userId;
@@ -286,7 +344,6 @@
         final long timestamp;
         final boolean defaultPermissionsGranted;
 
-
         VrState(boolean enabled, boolean running2dInVr, ComponentName targetPackageName, int userId,
                 int processId, ComponentName callingPackage) {
             this.enabled = enabled;
@@ -310,6 +367,39 @@
             this.defaultPermissionsGranted = defaultPermissionsGranted;
             this.timestamp = System.currentTimeMillis();
         }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            String tab = "  ";
+            String newLine = "\n";
+            StringBuilder sb = new StringBuilder(dateFormat.format(new Date(timestamp)));
+            sb.append(tab);
+            sb.append("State changed to:");
+            sb.append(tab);
+            sb.append((enabled) ? "ENABLED" : "DISABLED");
+            sb.append(newLine);
+            if (enabled) {
+                sb.append(tab);
+                sb.append("User=");
+                sb.append(userId);
+                sb.append(newLine);
+                sb.append(tab);
+                sb.append("Current VR Activity=");
+                sb.append((callingPackage == null) ? "None" : callingPackage.flattenToString());
+                sb.append(newLine);
+                sb.append(tab);
+                sb.append("Bound VrListenerService=");
+                sb.append((targetPackageName == null) ? "None"
+                        : targetPackageName.flattenToString());
+                sb.append(newLine);
+                if (defaultPermissionsGranted) {
+                    sb.append(tab);
+                    sb.append("Default permissions granted to the bound VrListenerService.");
+                    sb.append(newLine);
+                }
+            }
+            return sb.toString();
+        }
     }
 
     private static final BinderChecker sBinderChecker = new BinderChecker() {
@@ -490,6 +580,13 @@
         }
 
         @Override
+        public void setAndBindCompositor(String componentName) {
+            enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
+            VrManagerService.this.setAndBindCompositor(
+                (componentName == null) ? null : ComponentName.unflattenFromString(componentName));
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
@@ -497,6 +594,12 @@
             pw.println("VR mode is currently: " + ((mVrModeAllowed) ? "allowed" : "disallowed"));
             pw.println("Persistent VR mode is currently: " +
                     ((mPersistentVrModeEnabled) ? "enabled" : "disabled"));
+            pw.println("Currently bound VR listener service: "
+                    + ((mCurrentVrService == null)
+                    ? "None" : mCurrentVrService.getComponent().flattenToString()));
+            pw.println("Currently bound VR compositor service: "
+                    + ((mCurrentVrCompositorService == null)
+                    ? "None" : mCurrentVrCompositorService.getComponent().flattenToString()));
             pw.println("Previous state transitions:\n");
             String tab = "  ";
             dumpStateTransitions(pw);
@@ -582,6 +685,11 @@
         }
 
         @Override
+        public void onKeyguardStateChanged(boolean isShowing) {
+            VrManagerService.this.setKeyguardShowing(isShowing);
+        }
+
+        @Override
         public boolean isCurrentVrListener(String packageName, int userId) {
             return VrManagerService.this.isCurrentVrListener(packageName, userId);
         }
@@ -785,6 +893,7 @@
                         + mCurrentVrService.getComponent() + " for user "
                         + mCurrentVrService.getUserId());
                     mCurrentVrService.disconnect();
+                    updateCompositorServiceLocked(UserHandle.USER_NULL, null);
                     mCurrentVrService = null;
                 } else {
                     nothingChanged = true;
@@ -798,6 +907,7 @@
                         Slog.i(TAG, "VR mode component changed to " + component
                             + ", disconnecting " + mCurrentVrService.getComponent()
                             + " for user " + mCurrentVrService.getUserId());
+                        updateCompositorServiceLocked(UserHandle.USER_NULL, null);
                         createAndConnectService(component, userId);
                         sendUpdatedCaller = true;
                     } else {
@@ -985,7 +1095,7 @@
 
 
     private void createAndConnectService(@NonNull ComponentName component, int userId) {
-        mCurrentVrService = VrManagerService.create(mContext, component, userId);
+        mCurrentVrService = createVrListenerService(component, userId);
         mCurrentVrService.connect();
         Slog.i(TAG, "Connecting " + component + " for user " + userId);
     }
@@ -1020,13 +1130,27 @@
     }
 
     /**
-     * Helper function for making ManagedApplicationService instances.
+     * Helper function for making ManagedApplicationService for VrListenerService instances.
      */
-    private static ManagedApplicationService create(@NonNull Context context,
-            @NonNull ComponentName component, int userId) {
-        return ManagedApplicationService.build(context, component, userId,
+    private ManagedApplicationService createVrListenerService(@NonNull ComponentName component,
+            int userId) {
+        int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+                : ManagedApplicationService.RETRY_NEVER;
+        return ManagedApplicationService.build(mContext, component, userId,
                 R.string.vr_listener_binding_label, Settings.ACTION_VR_LISTENER_SETTINGS,
-                sBinderChecker);
+                sBinderChecker, /*isImportant*/true, retryType, mHandler, mEventCallback);
+    }
+
+    /**
+     * Helper function for making ManagedApplicationService for VR Compositor instances.
+     */
+    private ManagedApplicationService createVrCompositorService(@NonNull ComponentName component,
+            int userId) {
+        int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+                : ManagedApplicationService.RETRY_BEST_EFFORT;
+        return ManagedApplicationService.build(mContext, component, userId, /*clientLabel*/0,
+                /*settingsAction*/null, /*binderChecker*/null, /*isImportant*/true, retryType,
+                mHandler, /*disconnectCallback*/mEventCallback);
     }
 
     /**
@@ -1057,44 +1181,35 @@
 
     private void logStateLocked() {
         ComponentName currentBoundService = (mCurrentVrService == null) ? null :
-            mCurrentVrService.getComponent();
-        VrState current = new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
-            mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted);
-        if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
-            mLoggingDeque.removeFirst();
+                mCurrentVrService.getComponent();
+        logEvent(new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
+                mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted));
+    }
+
+    private void logEvent(LogFormattable event) {
+        synchronized (mLoggingDeque) {
+            if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
+                mLoggingDeque.removeFirst();
+                mLogLimitHit = true;
+            }
+            mLoggingDeque.add(event);
         }
-        mLoggingDeque.add(current);
     }
 
     private void dumpStateTransitions(PrintWriter pw) {
         SimpleDateFormat d = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-        String tab = "  ";
-        if (mLoggingDeque.size() == 0) {
-            pw.print(tab);
-            pw.println("None");
-        }
-        for (VrState state : mLoggingDeque) {
-            pw.print(d.format(new Date(state.timestamp)));
-            pw.print(tab);
-            pw.print("State changed to:");
-            pw.print(tab);
-            pw.println((state.enabled) ? "ENABLED" : "DISABLED");
-            if (state.enabled) {
-                pw.print(tab);
-                pw.print("User=");
-                pw.println(state.userId);
-                pw.print(tab);
-                pw.print("Current VR Activity=");
-                pw.println((state.callingPackage == null) ?
-                    "None" : state.callingPackage.flattenToString());
-                pw.print(tab);
-                pw.print("Bound VrListenerService=");
-                pw.println((state.targetPackageName == null) ?
-                    "None" : state.targetPackageName.flattenToString());
-                if (state.defaultPermissionsGranted) {
-                    pw.print(tab);
-                    pw.println("Default permissions granted to the bound VrListenerService.");
-                }
+        synchronized (mLoggingDeque) {
+            if (mLoggingDeque.size() == 0) {
+                pw.print("  ");
+                pw.println("None");
+            }
+
+            if (mLogLimitHit) {
+                pw.println("..."); // Indicates log overflow
+            }
+
+            for (LogFormattable event : mLoggingDeque) {
+                pw.println(event.toLogString(d));
             }
         }
     }
@@ -1177,10 +1292,41 @@
         return INVALID_DISPLAY;
     }
 
+    private void setAndBindCompositor(ComponentName componentName) {
+        final int userId = UserHandle.getCallingUserId();
+        final long token = Binder.clearCallingIdentity();
+        synchronized (mLock) {
+            updateCompositorServiceLocked(userId, componentName);
+        }
+        Binder.restoreCallingIdentity(token);
+    }
+
+    private void updateCompositorServiceLocked(int userId, ComponentName componentName) {
+        if (mCurrentVrCompositorService != null
+                && mCurrentVrCompositorService.disconnectIfNotMatching(componentName, userId)) {
+            Slog.i(TAG, "Disconnecting compositor service: "
+                    + mCurrentVrCompositorService.getComponent());
+            // Check if existing service matches the requested one, if not (or if the requested
+            // component is null) disconnect it.
+            mCurrentVrCompositorService = null;
+        }
+
+        if (componentName != null && mCurrentVrCompositorService == null) {
+            // We don't have an existing service matching the requested component, so attempt to
+            // connect one.
+            Slog.i(TAG, "Connecting compositor service: " + componentName);
+            mCurrentVrCompositorService = createVrCompositorService(componentName, userId);
+            mCurrentVrCompositorService.connect();
+        }
+    }
+
     private void setPersistentModeAndNotifyListenersLocked(boolean enabled) {
         if (mPersistentVrModeEnabled == enabled) {
             return;
         }
+        String eventName = "Persistent VR mode " + ((enabled) ? "enabled" : "disabled");
+        Slog.i(TAG, eventName);
+        logEvent(new SettingEvent(eventName));
         mPersistentVrModeEnabled = enabled;
 
         mHandler.sendMessage(mHandler.obtainMessage(MSG_PERSISTENT_VR_MODE_STATE_CHANGE,
diff --git a/com/android/server/webkit/SystemImpl.java b/com/android/server/webkit/SystemImpl.java
index bf769ed..1e334b8 100644
--- a/com/android/server/webkit/SystemImpl.java
+++ b/com/android/server/webkit/SystemImpl.java
@@ -304,6 +304,6 @@
 
     // flags declaring we want extra info from the package manager for webview providers
     private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
-            | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
-            | PackageManager.MATCH_ANY_USER;
+            | PackageManager.GET_SIGNATURES | PackageManager.GET_SHARED_LIBRARY_FILES
+            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING | PackageManager.MATCH_ANY_USER;
 }
diff --git a/com/android/server/wifi/NetworkListStoreData.java b/com/android/server/wifi/NetworkListStoreData.java
index 5ddfd4d..f287d4b 100644
--- a/com/android/server/wifi/NetworkListStoreData.java
+++ b/com/android/server/wifi/NetworkListStoreData.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.net.IpConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.os.Process;
 import android.util.Log;
 import android.util.Pair;
 
@@ -52,6 +54,8 @@
     private static final String XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION =
             "WifiEnterpriseConfiguration";
 
+    private final Context mContext;
+
     /**
      * List of saved shared networks visible to all the users to be stored in the shared store file.
      */
@@ -62,7 +66,9 @@
      */
     private List<WifiConfiguration> mUserConfigurations;
 
-    NetworkListStoreData() {}
+    NetworkListStoreData(Context context) {
+        mContext = context;
+    }
 
     @Override
     public void serializeData(XmlSerializer out, boolean shared)
@@ -282,6 +288,19 @@
                     "Configuration key does not match. Retrieved: " + configKeyParsed
                             + ", Calculated: " + configKeyCalculated);
         }
+        // Set creatorUid/creatorName for networks which don't have it set to valid value.
+        String creatorName = mContext.getPackageManager().getNameForUid(configuration.creatorUid);
+        if (creatorName == null) {
+            Log.e(TAG, "Invalid creatorUid for saved network " + configuration.configKey()
+                    + ", creatorUid=" + configuration.creatorUid);
+            configuration.creatorUid = Process.SYSTEM_UID;
+            configuration.creatorName = creatorName;
+        } else if (!creatorName.equals(configuration.creatorName)) {
+            Log.w(TAG, "Invalid creatorName for saved network " + configuration.configKey()
+                    + ", creatorUid=" + configuration.creatorUid
+                    + ", creatorName=" + configuration.creatorName);
+            configuration.creatorName = creatorName;
+        }
 
         configuration.setNetworkSelectionStatus(status);
         configuration.setIpConfiguration(ipConfiguration);
diff --git a/com/android/server/wifi/OpenNetworkNotifier.java b/com/android/server/wifi/OpenNetworkNotifier.java
index 31ff44b..eee4ac5 100644
--- a/com/android/server/wifi/OpenNetworkNotifier.java
+++ b/com/android/server/wifi/OpenNetworkNotifier.java
@@ -40,11 +40,13 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.FileDescriptor;
@@ -124,6 +126,7 @@
     private final Context mContext;
     private final Handler mHandler;
     private final FrameworkFacade mFrameworkFacade;
+    private final WifiMetrics mWifiMetrics;
     private final Clock mClock;
     private final WifiConfigManager mConfigManager;
     private final WifiStateMachine mWifiStateMachine;
@@ -138,6 +141,7 @@
             Looper looper,
             FrameworkFacade framework,
             Clock clock,
+            WifiMetrics wifiMetrics,
             WifiConfigManager wifiConfigManager,
             WifiConfigStore wifiConfigStore,
             WifiStateMachine wifiStateMachine,
@@ -146,6 +150,7 @@
         mContext = context;
         mHandler = new Handler(looper);
         mFrameworkFacade = framework;
+        mWifiMetrics = wifiMetrics;
         mClock = clock;
         mConfigManager = wifiConfigManager;
         mWifiStateMachine = wifiStateMachine;
@@ -206,7 +211,7 @@
             case WifiManager.CONNECT_NETWORK_SUCCEEDED:
                 break;
             case WifiManager.CONNECT_NETWORK_FAILED:
-                handleConnectionFailure();
+                handleConnectionAttemptFailedToSend();
                 break;
             default:
                 Log.e(TAG, "Unknown message " + msg.what);
@@ -226,6 +231,13 @@
 
         if (mState != STATE_NO_NOTIFICATION) {
             getNotificationManager().cancel(SystemMessage.NOTE_NETWORK_AVAILABLE);
+
+            if (mRecommendedNetwork != null) {
+                Log.d(TAG, "Notification with state="
+                        + mState
+                        + " was cleared for recommended network: "
+                        + mRecommendedNetwork.SSID);
+            }
             mState = STATE_NO_NOTIFICATION;
             mRecommendedNetwork = null;
         }
@@ -295,6 +307,10 @@
 
         postNotification(mNotificationBuilder.createNetworkConnectedNotification(
                 mRecommendedNetwork));
+
+        Log.d(TAG, "User connected to recommended network: " + mRecommendedNetwork.SSID);
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
         mState = STATE_CONNECTED_NOTIFICATION;
         mHandler.postDelayed(
                 () -> {
@@ -313,6 +329,10 @@
             return;
         }
         postNotification(mNotificationBuilder.createNetworkFailedNotification());
+
+        Log.d(TAG, "User failed to connect to recommended network: " + mRecommendedNetwork.SSID);
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
         mState = STATE_CONNECT_FAILED_NOTIFICATION;
         mHandler.postDelayed(
                 () -> {
@@ -328,8 +348,18 @@
     }
 
     private void postInitialNotification(ScanResult recommendedNetwork) {
+        if (mRecommendedNetwork != null
+                && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) {
+            return;
+        }
         postNotification(mNotificationBuilder.createConnectToNetworkNotification(
                 recommendedNetwork));
+        if (mState == STATE_NO_NOTIFICATION) {
+            mWifiMetrics.incrementConnectToNetworkNotification(
+                    ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        } else {
+            mWifiMetrics.incrementNumOpenNetworkRecommendationUpdates();
+        }
         mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION;
         mRecommendedNetwork = recommendedNetwork;
         mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay;
@@ -340,11 +370,15 @@
     }
 
     private void handleConnectToNetworkAction() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
         if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
             return;
         }
         postNotification(mNotificationBuilder.createNetworkConnectingNotification(
                 mRecommendedNetwork));
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
 
         Log.d(TAG, "User initiated connection to recommended network: " + mRecommendedNetwork.SSID);
         WifiConfiguration network = ScanResultUtil.createNetworkFromScanResult(mRecommendedNetwork);
@@ -366,6 +400,8 @@
     }
 
     private void handleSeeAllNetworksAction() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK);
         startWifiSettings();
     }
 
@@ -378,14 +414,26 @@
         clearPendingNotification(false /* resetRepeatTime */);
     }
 
+    private void handleConnectionAttemptFailedToSend() {
+        handleConnectionFailure();
+        mWifiMetrics.incrementNumOpenNetworkConnectMessageFailedToSend();
+    }
+
     private void handlePickWifiNetworkAfterConnectFailure() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount
+                        .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
         startWifiSettings();
     }
 
     private void handleUserDismissedAction() {
+        Log.d(TAG, "User dismissed notification with state=" + mState);
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION);
         if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
             // blacklist dismissed network
             mBlacklistedSsids.add(mRecommendedNetwork.SSID);
+            mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
             mConfigManager.saveToStore(false /* forceWrite */);
             Log.d(TAG, "Network is added to the open network notification blacklist: "
                     + mRecommendedNetwork.SSID);
@@ -418,6 +466,7 @@
         @Override
         public void setSsids(Set<String> ssidList) {
             mBlacklistedSsids.addAll(ssidList);
+            mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
         }
     }
 
@@ -440,8 +489,10 @@
         }
 
         private boolean getValue() {
-            return mFrameworkFacade.getIntegerSetting(mContext,
+            boolean enabled = mFrameworkFacade.getIntegerSetting(mContext,
                     Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+            mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(enabled);
+            return enabled;
         }
     }
 }
diff --git a/com/android/server/wifi/VelocityBasedConnectedScore.java b/com/android/server/wifi/VelocityBasedConnectedScore.java
new file mode 100644
index 0000000..9d90332
--- /dev/null
+++ b/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -0,0 +1,133 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+import com.android.server.wifi.util.KalmanFilter;
+import com.android.server.wifi.util.Matrix;
+
+/**
+ * Class used to calculate scores for connected wifi networks and report it to the associated
+ * network agent.
+ */
+public class VelocityBasedConnectedScore extends ConnectedScore {
+
+    // Device configs. The values are examples.
+    private final int mThresholdMinimumRssi5;      // -82
+    private final int mThresholdMinimumRssi24;     // -85
+
+    private int mFrequency = 5000;
+    private int mRssi = 0;
+    private final KalmanFilter mFilter;
+    private long mLastMillis;
+
+    public VelocityBasedConnectedScore(Context context, Clock clock) {
+        super(clock);
+        mThresholdMinimumRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdMinimumRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mFilter = new KalmanFilter();
+        mFilter.mH = new Matrix(2, new double[]{1.0, 0.0});
+        mFilter.mR = new Matrix(1, new double[]{1.0});
+    }
+
+    /**
+     * Set the Kalman filter's state transition matrix F and process noise covariance Q given
+     * a time step.
+     *
+     * @param dt delta time, in seconds
+     */
+    private void setDeltaTimeSeconds(double dt) {
+        mFilter.mF = new Matrix(2, new double[]{1.0, dt, 0.0, 1.0});
+        Matrix tG = new Matrix(1, new double[]{0.5 * dt * dt, dt});
+        double stda = 0.02; // standard deviation of modelled acceleration
+        mFilter.mQ = tG.dotTranspose(tG).dot(new Matrix(2, new double[]{
+                stda * stda, 0.0,
+                0.0, stda * stda}));
+    }
+    /**
+     * Reset the filter state.
+     */
+    @Override
+    public void reset() {
+        mLastMillis = 0;
+    }
+
+    /**
+     * Updates scoring state using RSSI and measurement noise estimate
+     * <p>
+     * This is useful if an RSSI comes from another source (e.g. scan results) and the
+     * expected noise varies by source.
+     *
+     * @param rssi              signal strength (dB).
+     * @param millis            millisecond-resolution time.
+     * @param standardDeviation of the RSSI.
+     */
+    @Override
+    public void updateUsingRssi(int rssi, long millis, double standardDeviation) {
+        if (millis <= 0) return;
+        if (mLastMillis <= 0 || millis < mLastMillis) {
+            double initialVariance = 9.0 * standardDeviation * standardDeviation;
+            mFilter.mx = new Matrix(1, new double[]{rssi, 0.0});
+            mFilter.mP = new Matrix(2, new double[]{initialVariance, 0.0, 0.0, 0.0});
+            mLastMillis = millis;
+            return;
+        }
+        double dt = (millis - mLastMillis) * 0.001;
+        mFilter.mR.put(0, 0, standardDeviation * standardDeviation);
+        setDeltaTimeSeconds(dt);
+        mFilter.predict();
+        mLastMillis = millis;
+        mFilter.update(new Matrix(1, new double[]{rssi}));
+    }
+
+    /**
+     * Updates the state.
+     */
+    @Override
+    public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
+        int frequency = wifiInfo.getFrequency();
+        if (frequency != mFrequency) {
+            reset(); // Probably roamed
+            mFrequency = frequency;
+        }
+        updateUsingRssi(wifiInfo.getRssi(), millis, mDefaultRssiStandardDeviation);
+    }
+
+    /**
+     * Velocity scorer - predict the rssi a few seconds from now
+     */
+    @Override
+    public int generateScore() {
+        int badRssi = mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
+        double horizonSeconds = 15.0;
+        Matrix x = new Matrix(mFilter.mx);
+        double filteredRssi = x.get(0, 0);
+        setDeltaTimeSeconds(horizonSeconds);
+        x = mFilter.mF.dot(x);
+        double forecastRssi = x.get(0, 0);
+        if (forecastRssi > filteredRssi) {
+            forecastRssi = filteredRssi; // Be pessimistic about predicting an actual increase
+        }
+        int score = (int) (Math.round(forecastRssi) - badRssi) + WIFI_TRANSITION_SCORE;
+        return score;
+    }
+}
diff --git a/com/android/server/wifi/WifiBackupRestore.java b/com/android/server/wifi/WifiBackupRestore.java
index 60c3b48..ae5e411 100644
--- a/com/android/server/wifi/WifiBackupRestore.java
+++ b/com/android/server/wifi/WifiBackupRestore.java
@@ -228,9 +228,8 @@
             }
 
             return parseNetworkConfigurationsFromXml(in, rootTagDepth, version);
-        } catch (XmlPullParserException e) {
-            Log.e(TAG, "Error parsing the backup data: " + e);
-        } catch (IOException e) {
+        } catch (XmlPullParserException | IOException | ClassCastException
+                | IllegalArgumentException e) {
             Log.e(TAG, "Error parsing the backup data: " + e);
         }
         return null;
diff --git a/com/android/server/wifi/WifiConfigManager.java b/com/android/server/wifi/WifiConfigManager.java
index f8b33cb..ba1695a 100644
--- a/com/android/server/wifi/WifiConfigManager.java
+++ b/com/android/server/wifi/WifiConfigManager.java
@@ -2333,12 +2333,13 @@
     public List<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList() {
         List<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
         List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
-        // Remove any permanently disabled networks.
+        // Remove any permanently or temporarily disabled networks.
         Iterator<WifiConfiguration> iter = networks.iterator();
         while (iter.hasNext()) {
             WifiConfiguration config = iter.next();
             if (config.ephemeral || config.isPasspoint()
-                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
+                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+                    || config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
                 iter.remove();
             }
         }
@@ -2574,10 +2575,12 @@
      * @param userId The identifier of the user that stopped.
      */
     public void handleUserStop(int userId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Handling user stop for " + userId);
+        }
         if (userId == mCurrentUserId && mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
             saveToStore(true);
-            clearInternalData();
-            mCurrentUserId = UserHandle.USER_SYSTEM;
+            clearInternalUserData(mCurrentUserId);
         }
     }
 
@@ -2589,6 +2592,7 @@
      *  - List of deleted ephemeral networks.
      */
     private void clearInternalData() {
+        localLog("clearInternalData: Clearing all internal data");
         mConfiguredNetworks.clear();
         mDeletedEphemeralSSIDs.clear();
         mScanDetailCaches.clear();
@@ -2607,12 +2611,16 @@
      * removed from memory.
      */
     private Set<Integer> clearInternalUserData(int userId) {
+        localLog("clearInternalUserData: Clearing user internal data for " + userId);
         Set<Integer> removedNetworkIds = new HashSet<>();
         // Remove any private networks of the old user before switching the userId.
         for (WifiConfiguration config : getInternalConfiguredNetworks()) {
             if (!config.shared && WifiConfigurationUtil.doesUidBelongToAnyProfile(
                     config.creatorUid, mUserManager.getProfiles(userId))) {
                 removedNetworkIds.add(config.networkId);
+                localLog("clearInternalUserData: removed config."
+                        + " netId=" + config.networkId
+                        + " configKey=" + config.configKey());
                 mConfiguredNetworks.remove(config.networkId);
             }
         }
diff --git a/com/android/server/wifi/WifiConnectivityManager.java b/com/android/server/wifi/WifiConnectivityManager.java
index 7e730c8..458f73a 100644
--- a/com/android/server/wifi/WifiConnectivityManager.java
+++ b/com/android/server/wifi/WifiConnectivityManager.java
@@ -30,6 +30,8 @@
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
+import android.os.WorkSource;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -214,7 +216,7 @@
 
         @Override
         public void onAlarm() {
-            startSingleScan(mIsFullBandScan);
+            startSingleScan(mIsFullBandScan, WIFI_WORK_SOURCE);
         }
     }
 
@@ -741,7 +743,7 @@
                 localLog("connectToNetwork: Connect to " + targetAssociationId + " from "
                         + currentAssociationId);
             }
-            mStateMachine.startConnectToNetwork(candidate.networkId, targetBssid);
+            mStateMachine.startConnectToNetwork(candidate.networkId, Process.WIFI_UID, targetBssid);
         }
     }
 
@@ -794,7 +796,7 @@
             localLog("start a single scan from watchdogHandler");
 
             scheduleWatchdogTimer();
-            startSingleScan(true);
+            startSingleScan(true, WIFI_WORK_SOURCE);
         }
     }
 
@@ -823,7 +825,7 @@
         }
 
         mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
-        startSingleScan(isFullBandScan);
+        startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
         schedulePeriodicScanTimer(mPeriodicSingleScanInterval);
 
         // Set up the next scan interval in an exponential backoff fashion.
@@ -850,7 +852,7 @@
     }
 
     // Start a single scan
-    private void startSingleScan(boolean isFullBandScan) {
+    private void startSingleScan(boolean isFullBandScan, WorkSource workSource) {
         if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
             return;
         }
@@ -875,7 +877,7 @@
 
         SingleScanListener singleScanListener =
                 new SingleScanListener(isFullBandScan);
-        mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
+        mScanner.startScan(settings, singleScanListener, workSource);
     }
 
     // Start a periodic scan when screen is on
@@ -1132,11 +1134,11 @@
     /**
      * Handler for on-demand connectivity scan
      */
-    public void forceConnectivityScan() {
-        localLog("forceConnectivityScan");
+    public void forceConnectivityScan(WorkSource workSource) {
+        localLog("forceConnectivityScan in request of " + workSource);
 
         mWaitForFullBandScanResults = true;
-        startSingleScan(true);
+        startSingleScan(true, workSource);
     }
 
     /**
diff --git a/com/android/server/wifi/WifiCountryCode.java b/com/android/server/wifi/WifiCountryCode.java
index e69fb8e..66a035f 100644
--- a/com/android/server/wifi/WifiCountryCode.java
+++ b/com/android/server/wifi/WifiCountryCode.java
@@ -83,11 +83,9 @@
     public synchronized void simCardRemoved() {
         if (DBG) Log.d(TAG, "SIM Card Removed");
         // SIM card is removed, we need to reset the country code to phone default.
-        if (mRevertCountryCodeOnCellularLoss) {
-            mTelephonyCountryCode = null;
-            if (mReady) {
-                updateCountryCode();
-            }
+        mTelephonyCountryCode = null;
+        if (mReady) {
+            updateCountryCode();
         }
     }
 
@@ -98,12 +96,9 @@
      */
     public synchronized void airplaneModeEnabled() {
         if (DBG) Log.d(TAG, "Airplane Mode Enabled");
-        mTelephonyCountryCode = null;
         // Airplane mode is enabled, we need to reset the country code to phone default.
-        if (mRevertCountryCodeOnCellularLoss) {
-            mTelephonyCountryCode = null;
-            // Country code will be set upon when wpa_supplicant starts next time.
-        }
+        // Country code will be set upon when wpa_supplicant starts next time.
+        mTelephonyCountryCode = null;
     }
 
     /**
@@ -133,8 +128,10 @@
         if (DBG) Log.d(TAG, "Receive set country code request: " + countryCode);
         // Empty country code.
         if (TextUtils.isEmpty(countryCode)) {
-            if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
-            mTelephonyCountryCode = null;
+            if (mRevertCountryCodeOnCellularLoss) {
+                if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
+                mTelephonyCountryCode = null;
+            }
         } else {
             mTelephonyCountryCode = countryCode.toUpperCase();
         }
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index fc3af83..1cbb29e 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -116,6 +116,7 @@
     private final PasspointManager mPasspointManager;
     private final SIMAccessor mSimAccessor;
     private HandlerThread mWifiAwareHandlerThread;
+    private HandlerThread mRttHandlerThread;
     private HalDeviceManager mHalDeviceManager;
     private final IBatteryStats mBatteryStats;
     private final WifiStateTracker mWifiStateTracker;
@@ -197,7 +198,7 @@
         mWifiConfigManager = new WifiConfigManager(mContext, mClock,
                 UserManager.get(mContext), TelephonyManager.from(mContext),
                 mWifiKeyStore, mWifiConfigStore, mWifiPermissionsUtil,
-                mWifiPermissionsWrapper, new NetworkListStoreData(),
+                mWifiPermissionsWrapper, new NetworkListStoreData(mContext),
                 new DeletedEphemeralSsidsStoreData());
         mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
         mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
@@ -225,7 +226,7 @@
                 new WrongPasswordNotifier(mContext, mFrameworkFacade));
         mCertManager = new WifiCertManager(mContext);
         mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
-                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock,
+                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
                 mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
                 new OpenNetworkRecommender(),
                 new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
@@ -455,6 +456,19 @@
     }
 
     /**
+     * Returns a singleton instance of a HandlerThread for injection. Uses lazy initialization.
+     *
+     * TODO: share worker thread with other Wi-Fi handlers (b/27924886)
+     */
+    public HandlerThread getRttHandlerThread() {
+        if (mRttHandlerThread == null) { // lazy initialization
+            mRttHandlerThread = new HandlerThread("wifiRttService");
+            mRttHandlerThread.start();
+        }
+        return mRttHandlerThread;
+    }
+
+    /**
      * Returns a single instance of HalDeviceManager for injection.
      */
     public HalDeviceManager getHalDeviceManager() {
diff --git a/com/android/server/wifi/WifiMetrics.java b/com/android/server/wifi/WifiMetrics.java
index 5db5ee6..071b4f8 100644
--- a/com/android/server/wifi/WifiMetrics.java
+++ b/com/android/server/wifi/WifiMetrics.java
@@ -37,6 +37,7 @@
 import com.android.server.wifi.hotspot2.PasspointMatch;
 import com.android.server.wifi.hotspot2.PasspointProvider;
 import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
@@ -83,6 +84,7 @@
     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;
+    private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
@@ -150,6 +152,15 @@
     private final SparseIntArray mAvailableSavedPasspointProviderBssidsInScanHistogram =
             new SparseIntArray();
 
+    /** Mapping of "Connect to Network" notifications to counts. */
+    private final SparseIntArray mConnectToNetworkNotificationCount = new SparseIntArray();
+    /** Mapping of "Connect to Network" notification user actions to counts. */
+    private final SparseIntArray mConnectToNetworkNotificationActionCount = new SparseIntArray();
+    private int mOpenNetworkRecommenderBlacklistSize = 0;
+    private boolean mIsWifiNetworksAvailableNotificationOn = false;
+    private int mNumOpenNetworkConnectMessageFailedToSend = 0;
+    private int mNumOpenNetworkRecommendationUpdates = 0;
+
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -1237,6 +1248,55 @@
         }
     }
 
+    /** Increments the occurence of a "Connect to Network" notification. */
+    public void incrementConnectToNetworkNotification(int notificationType) {
+        synchronized (mLock) {
+            int count = mConnectToNetworkNotificationCount.get(notificationType);
+            mConnectToNetworkNotificationCount.put(notificationType, count + 1);
+        }
+    }
+
+    /** Increments the occurence of an "Connect to Network" notification user action. */
+    public void incrementConnectToNetworkNotificationAction(int notificationType, int actionType) {
+        synchronized (mLock) {
+            int key = notificationType * CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER
+                    + actionType;
+            int count = mConnectToNetworkNotificationActionCount.get(key);
+            mConnectToNetworkNotificationActionCount.put(key, count + 1);
+        }
+    }
+
+    /**
+     * Sets the number of SSIDs blacklisted from recommendation by the open network notification
+     * recommender.
+     */
+    public void setOpenNetworkRecommenderBlacklistSize(int size) {
+        synchronized (mLock) {
+            mOpenNetworkRecommenderBlacklistSize = size;
+        }
+    }
+
+    /** Sets if the available network notification feature is enabled. */
+    public void setIsWifiNetworksAvailableNotificationEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mIsWifiNetworksAvailableNotificationOn = enabled;
+        }
+    }
+
+    /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+    public void incrementNumOpenNetworkRecommendationUpdates() {
+        synchronized (mLock) {
+            mNumOpenNetworkRecommendationUpdates++;
+        }
+    }
+
+    /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+    public void incrementNumOpenNetworkConnectMessageFailedToSend() {
+        synchronized (mLock) {
+            mNumOpenNetworkConnectMessageFailedToSend++;
+        }
+    }
+
     public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
     public static final String CLEAN_DUMP_ARG = "clean";
 
@@ -1488,6 +1548,19 @@
                         + mPnoScanMetrics.numPnoScanFailedOverOffload);
                 pw.println("mPnoScanMetrics.numPnoFoundNetworkEvents="
                         + mPnoScanMetrics.numPnoFoundNetworkEvents);
+
+                pw.println("mWifiLogProto.connectToNetworkNotificationCount="
+                        + mConnectToNetworkNotificationCount.toString());
+                pw.println("mWifiLogProto.connectToNetworkNotificationActionCount="
+                        + mConnectToNetworkNotificationActionCount.toString());
+                pw.println("mWifiLogProto.openNetworkRecommenderBlacklistSize="
+                        + mOpenNetworkRecommenderBlacklistSize);
+                pw.println("mWifiLogProto.isWifiNetworksAvailableNotificationOn="
+                        + mIsWifiNetworksAvailableNotificationOn);
+                pw.println("mWifiLogProto.numOpenNetworkRecommendationUpdates="
+                        + mNumOpenNetworkRecommendationUpdates);
+                pw.println("mWifiLogProto.numOpenNetworkConnectMessageFailedToSend="
+                        + mNumOpenNetworkConnectMessageFailedToSend);
             }
         }
     }
@@ -1698,6 +1771,53 @@
             mWifiLogProto.wifiAwareLog = mWifiAwareMetrics.consolidateProto();
 
             mWifiLogProto.pnoScanMetrics = mPnoScanMetrics;
+
+            /**
+             * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+             * proto's repeated IntKeyVal array.
+             */
+            ConnectToNetworkNotificationAndActionCount[] notificationCountArray =
+                    new ConnectToNetworkNotificationAndActionCount[
+                            mConnectToNetworkNotificationCount.size()];
+            for (int i = 0; i < mConnectToNetworkNotificationCount.size(); i++) {
+                ConnectToNetworkNotificationAndActionCount keyVal =
+                        new ConnectToNetworkNotificationAndActionCount();
+                keyVal.notification = mConnectToNetworkNotificationCount.keyAt(i);
+                keyVal.recommender =
+                        ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+                keyVal.count = mConnectToNetworkNotificationCount.valueAt(i);
+                notificationCountArray[i] = keyVal;
+            }
+            mWifiLogProto.connectToNetworkNotificationCount = notificationCountArray;
+
+            /**
+             * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+             * proto's repeated IntKeyVal array.
+             */
+            ConnectToNetworkNotificationAndActionCount[] notificationActionCountArray =
+                    new ConnectToNetworkNotificationAndActionCount[
+                            mConnectToNetworkNotificationActionCount.size()];
+            for (int i = 0; i < mConnectToNetworkNotificationActionCount.size(); i++) {
+                ConnectToNetworkNotificationAndActionCount keyVal =
+                        new ConnectToNetworkNotificationAndActionCount();
+                int key = mConnectToNetworkNotificationActionCount.keyAt(i);
+                keyVal.notification = key / CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+                keyVal.action = key % CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+                keyVal.recommender =
+                        ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+                keyVal.count = mConnectToNetworkNotificationActionCount.valueAt(i);
+                notificationActionCountArray[i] = keyVal;
+            }
+            mWifiLogProto.connectToNetworkNotificationActionCount = notificationActionCountArray;
+
+            mWifiLogProto.openNetworkRecommenderBlacklistSize =
+                    mOpenNetworkRecommenderBlacklistSize;
+            mWifiLogProto.isWifiNetworksAvailableNotificationOn =
+                    mIsWifiNetworksAvailableNotificationOn;
+            mWifiLogProto.numOpenNetworkRecommendationUpdates =
+                    mNumOpenNetworkRecommendationUpdates;
+            mWifiLogProto.numOpenNetworkConnectMessageFailedToSend =
+                    mNumOpenNetworkConnectMessageFailedToSend;
         }
     }
 
@@ -1716,7 +1836,8 @@
     }
 
     /**
-     * Clear all WifiMetrics, except for currentConnectionEvent.
+     * Clear all WifiMetrics, except for currentConnectionEvent and Open Network Notification
+     * feature enabled state, blacklist size.
      */
     private void clear() {
         synchronized (mLock) {
@@ -1747,6 +1868,10 @@
             mAvailableSavedPasspointProviderProfilesInScanHistogram.clear();
             mAvailableSavedPasspointProviderBssidsInScanHistogram.clear();
             mPnoScanMetrics.clear();
+            mConnectToNetworkNotificationCount.clear();
+            mConnectToNetworkNotificationActionCount.clear();
+            mNumOpenNetworkRecommendationUpdates = 0;
+            mNumOpenNetworkConnectMessageFailedToSend = 0;
         }
     }
 
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index 5b12a36..0b1719d 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -79,6 +79,10 @@
         return mInterfaceName;
     }
 
+    public WifiVendorHal getVendorHal() {
+        return mWifiVendorHal;
+    }
+
     /**
      * Enable verbose logging for all sub modules.
      */
diff --git a/com/android/server/wifi/WifiScoreReport.java b/com/android/server/wifi/WifiScoreReport.java
index 894d57c..324cdba 100644
--- a/com/android/server/wifi/WifiScoreReport.java
+++ b/com/android/server/wifi/WifiScoreReport.java
@@ -49,11 +49,13 @@
 
     ConnectedScore mConnectedScore;
     ConnectedScore mAggressiveConnectedScore;
+    ConnectedScore mFancyConnectedScore;
 
     WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
         mClock = clock;
         mConnectedScore = new LegacyConnectedScore(context, wifiConfigManager, clock);
         mAggressiveConnectedScore = new AggressiveConnectedScore(context, clock);
+        mFancyConnectedScore = new VelocityBasedConnectedScore(context, clock);
     }
 
     /**
@@ -76,6 +78,7 @@
         }
         mConnectedScore.reset();
         mAggressiveConnectedScore.reset();
+        mFancyConnectedScore.reset();
         if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
     }
 
@@ -113,18 +116,20 @@
                                         int aggressiveHandover, WifiMetrics wifiMetrics) {
         int score;
 
-        long millis = mConnectedScore.getMillis();
+        long millis = mClock.getWallClockMillis();
 
         mConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
         mAggressiveConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
+        mFancyConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
 
         int s0 = mConnectedScore.generateScore();
         int s1 = mAggressiveConnectedScore.generateScore();
+        int s2 = mFancyConnectedScore.generateScore();
 
         if (aggressiveHandover == 0) {
             score = s0;
         } else {
-            score = s1;
+            score = s2;
         }
 
         //sanitize boundaries
@@ -135,7 +140,7 @@
             score = 0;
         }
 
-        logLinkMetrics(wifiInfo, s0, s1);
+        logLinkMetrics(wifiInfo, millis, s0, s1, s2);
 
         //report score
         if (score != wifiInfo.score) {
@@ -163,8 +168,7 @@
     /**
      * Data logging for dumpsys
      */
-    private void logLinkMetrics(WifiInfo wifiInfo, int s0, int s1) {
-        long now = mClock.getWallClockMillis();
+    private void logLinkMetrics(WifiInfo wifiInfo, long now, int s0, int s1, int s2) {
         if (now < FIRST_REASONABLE_WALL_CLOCK) return;
         double rssi = wifiInfo.getRssi();
         int freq = wifiInfo.getFrequency();
@@ -176,10 +180,10 @@
         try {
             String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
             String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
-                    "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d",
+                    "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
                     timestamp, mSessionNumber, rssi, freq, linkSpeed,
                     txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
-                    s0, s1);
+                    s0, s1, s2);
             mLinkMetricsHistory.add(s);
         } catch (Exception e) {
             Log.e(TAG, "format problem", e);
@@ -201,7 +205,7 @@
      * @param args unused
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1");
+        pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
         for (String line : mLinkMetricsHistory) {
             pw.println(line);
         }
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index bb995b7..c83f6c6 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -1421,7 +1421,7 @@
     public void reconnect() {
         enforceChangePermission();
         mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
-        mWifiStateMachine.reconnectCommand();
+        mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
     }
 
     /**
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index faad0e2..1f6cada 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -1321,7 +1321,7 @@
 
     /**
      * Initiates connection to a network specified by the user/app. This method checks if the
-     * requesting app holds the WIFI_CONFIG_OVERRIDE permission.
+     * requesting app holds the NETWORK_SETTINGS permission.
      *
      * @param netId Id network to initiate connection.
      * @param uid UID of the app requesting the connection.
@@ -1350,7 +1350,7 @@
             logi("connectToUserSelectNetwork already connecting/connected=" + netId);
         } else {
             mWifiConnectivityManager.prepareForForcedConnection(netId);
-            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+            startConnectToNetwork(netId, uid, SUPPLICANT_BSSID_ANY);
         }
         return true;
     }
@@ -1873,8 +1873,8 @@
     /**
      * Initiate a reconnection to AP
      */
-    public void reconnectCommand() {
-        sendMessage(CMD_RECONNECT);
+    public void reconnectCommand(WorkSource workSource) {
+        sendMessage(CMD_RECONNECT, workSource);
     }
 
     /**
@@ -3980,9 +3980,7 @@
                     deleteNetworkConfigAndSendReply(message, true);
                     break;
                 case WifiManager.SAVE_NETWORK:
-                    messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                    replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                            WifiManager.BUSY);
+                    saveNetworkConfigAndSendReply(message);
                     break;
                 case WifiManager.START_WPS:
                     replyToMessage(message, WifiManager.WPS_FAILED,
@@ -4537,7 +4535,7 @@
                     mEnableAutoJoinWhenAssociated = allowed;
                     if (!old_state && allowed && mScreenOn
                             && getCurrentState() == mConnectedState) {
-                        mWifiConnectivityManager.forceConnectivityScan();
+                        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
                     }
                     break;
                 case CMD_SELECT_TX_POWER_SCENARIO:
@@ -4824,7 +4822,7 @@
 
             // Notify PasspointManager of Passpoint network connected event.
             WifiConfiguration currentNetwork = getCurrentWifiConfiguration();
-            if (currentNetwork.isPasspoint()) {
+            if (currentNetwork != null && currentNetwork.isPasspoint()) {
                 mPasspointManager.onPasspointNetworkConnected(currentNetwork.FQDN);
             }
        }
@@ -5160,7 +5158,8 @@
                             mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
                     break;
                 case CMD_RECONNECT:
-                    mWifiConnectivityManager.forceConnectivityScan();
+                    WorkSource workSource = (WorkSource) message.obj;
+                    mWifiConnectivityManager.forceConnectivityScan(workSource);
                     break;
                 case CMD_REASSOCIATE:
                     lastConnectAttemptTimestamp = mClock.getWallClockMillis();
@@ -5180,7 +5179,23 @@
                 case CMD_START_CONNECT:
                     /* connect command coming from auto-join */
                     netId = message.arg1;
+                    int uid = message.arg2;
                     bssid = (String) message.obj;
+
+                    synchronized (mWifiReqCountLock) {
+                        if (!hasConnectionRequests()) {
+                            if (mNetworkAgent == null) {
+                                loge("CMD_START_CONNECT but no requests and not connected,"
+                                        + " bailing");
+                                break;
+                            } else if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+                                loge("CMD_START_CONNECT but no requests and connected, but app "
+                                        + "does not have sufficient permissions, bailing");
+                                break;
+                            }
+                        }
+                    }
+
                     config = mWifiConfigManager.getConfiguredNetworkWithPassword(netId);
                     logd("CMD_START_CONNECT sup state "
                             + mSupplicantStateTracker.getSupplicantStateName()
@@ -5270,41 +5285,17 @@
                     replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.SAVE_NETWORK:
-                    config = (WifiConfiguration) message.obj;
-                    mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
-                    if (config == null) {
-                        loge("SAVE_NETWORK with null configuration"
-                                + mSupplicantStateTracker.getSupplicantStateName()
-                                + " my state " + getCurrentState().getName());
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
-                    result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
-                    if (!result.isSuccess()) {
-                        loge("SAVE_NETWORK adding/updating config=" + config + " failed");
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
-                    if (!mWifiConfigManager.enableNetwork(
-                            result.getNetworkId(), false, message.sendingUid)) {
-                        loge("SAVE_NETWORK enabling config=" + config + " failed");
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
+                    result = saveNetworkConfigAndSendReply(message);
                     netId = result.getNetworkId();
-                    if (mWifiInfo.getNetworkId() == netId) {
+                    if (result.isSuccess() && mWifiInfo.getNetworkId() == netId) {
+                        mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
                         if (result.hasCredentialChanged()) {
+                            config = (WifiConfiguration) message.obj;
                             // The network credentials changed and we're connected to this network,
                             // start a new connection with the updated credentials.
                             logi("SAVE_NETWORK credential changed for config=" + config.configKey()
                                     + ", Reconnecting.");
-                            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+                            startConnectToNetwork(netId, message.sendingUid, SUPPLICANT_BSSID_ANY);
                         } else {
                             if (result.hasProxyChanged()) {
                                 log("Reconfiguring proxy on connection");
@@ -5322,8 +5313,6 @@
                             }
                         }
                     }
-                    broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
-                    replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.FORGET_NETWORK:
                     if (!deleteNetworkConfigAndSendReply(message, true)) {
@@ -5831,8 +5820,15 @@
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_NONE,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    sendConnectedState();
-                    transitionTo(mConnectedState);
+                    if (getCurrentWifiConfiguration() == null) {
+                        // The current config may have been removed while we were connecting,
+                        // trigger a disconnect to clear up state.
+                        mWifiNative.disconnect();
+                        transitionTo(mDisconnectingState);
+                    } else {
+                        sendConnectedState();
+                        transitionTo(mConnectedState);
+                    }
                     break;
                 case CMD_IP_CONFIGURATION_LOST:
                     // Get Link layer stats so that we get fresh tx packet counters.
@@ -5999,6 +5995,9 @@
                 case CMD_STOP_RSSI_MONITORING_OFFLOAD:
                     stopRssiMonitoringOffload();
                     break;
+                case CMD_RECONNECT:
+                    log(" Ignore CMD_RECONNECT request because wifi is already connected");
+                    break;
                 case CMD_RESET_SIM_NETWORKS:
                     if (message.arg1 == 0 // sim was removed
                             && mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
@@ -6142,7 +6141,7 @@
         WifiConfiguration config = getCurrentWifiConfiguration();
         if (shouldEvaluateWhetherToSendExplicitlySelected(config)) {
             boolean prompt =
-                    mWifiPermissionsUtil.checkConfigOverridePermission(config.lastConnectUid);
+                    mWifiPermissionsUtil.checkNetworkSettingsPermission(config.lastConnectUid);
             if (mVerboseLoggingEnabled) {
                 log("Network selected by UID " + config.lastConnectUid + " prompt=" + prompt);
             }
@@ -6851,6 +6850,8 @@
                 case WifiManager.CONNECT_NETWORK:
                 case CMD_ENABLE_NETWORK:
                 case CMD_RECONNECT:
+                    log(" Ignore CMD_RECONNECT request because wps is running");
+                    return HANDLED;
                 case CMD_REASSOCIATE:
                     deferMessage(message);
                     break;
@@ -7118,14 +7119,11 @@
      * Automatically connect to the network specified
      *
      * @param networkId ID of the network to connect to
+     * @param uid UID of the app triggering the connection.
      * @param bssid BSSID of the network
      */
-    public void startConnectToNetwork(int networkId, String bssid) {
-        synchronized (mWifiReqCountLock) {
-            if (hasConnectionRequests()) {
-                sendMessage(CMD_START_CONNECT, networkId, 0, bssid);
-            }
-        }
+    public void startConnectToNetwork(int networkId, int uid, String bssid) {
+        sendMessage(CMD_START_CONNECT, networkId, uid, bssid);
     }
 
     /**
@@ -7210,6 +7208,43 @@
         }
     }
 
+    /**
+     * Private method to handle calling WifiConfigManager to add & enable network configs and reply
+     * to the message from the sender of the outcome.
+     *
+     * @return NetworkUpdateResult with networkId of the added/updated configuration. Will return
+     * {@link WifiConfiguration#INVALID_NETWORK_ID} in case of error.
+     */
+    private NetworkUpdateResult saveNetworkConfigAndSendReply(Message message) {
+        WifiConfiguration config = (WifiConfiguration) message.obj;
+        if (config == null) {
+            loge("SAVE_NETWORK with null configuration "
+                    + mSupplicantStateTracker.getSupplicantStateName()
+                    + " my state " + getCurrentState().getName());
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
+        if (!result.isSuccess()) {
+            loge("SAVE_NETWORK adding/updating config=" + config + " failed");
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return result;
+        }
+        if (!mWifiConfigManager.enableNetwork(
+                result.getNetworkId(), false, message.sendingUid)) {
+            loge("SAVE_NETWORK enabling config=" + config + " failed");
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+        return result;
+    }
+
     private static String getLinkPropertiesSummary(LinkProperties lp) {
         List<String> attributes = new ArrayList<>(6);
         if (lp.hasIPv4Address()) {
diff --git a/com/android/server/wifi/WifiVendorHal.java b/com/android/server/wifi/WifiVendorHal.java
index 12674aa..e0467d7 100644
--- a/com/android/server/wifi/WifiVendorHal.java
+++ b/com/android/server/wifi/WifiVendorHal.java
@@ -67,6 +67,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.Log;
 import android.util.MutableBoolean;
 import android.util.MutableInt;
 
@@ -237,6 +238,11 @@
         clearState();
     }
 
+    // TODO: b/65014872 remove - have RttService (RTT2) interact directly with HalDeviceManager
+    public IWifiRttController getRttController() {
+        return mIWifiRttController;
+    }
+
     private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
 
     /**
@@ -2582,8 +2588,25 @@
                 // The problem here is that the two threads acquire the locks in opposite order.
                 // If, for example, T2.2 executes between T1.2 and 1.4, then T1 and T2
                 // will be deadlocked.
-                eventHandler.onRingBufferData(
-                        ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+                int sizeBefore = data.size();
+                boolean conversionFailure = false;
+                try {
+                    eventHandler.onRingBufferData(
+                            ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+                    int sizeAfter = data.size();
+                    if (sizeAfter != sizeBefore) {
+                        conversionFailure = true;
+                    }
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    conversionFailure = true;
+                }
+                if (conversionFailure) {
+                    Log.wtf("WifiVendorHal", "Conversion failure detected in "
+                            + "onDebugRingBufferDataAvailable. "
+                            + "The input ArrayList |data| is potentially corrupted. "
+                            + "Starting size=" + sizeBefore + ", "
+                            + "final size=" + data.size());
+                }
             });
         }
 
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index 056777f..b6104a8 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -374,8 +374,13 @@
                         new InformationElementUtil.Capabilities();
                 capabilities.from(ies, result.capability);
                 String flags = capabilities.generateCapabilitiesString();
-                NetworkDetail networkDetail =
-                        new NetworkDetail(bssid, ies, null, result.frequency);
+                NetworkDetail networkDetail;
+                try {
+                    networkDetail = new NetworkDetail(bssid, ies, null, result.frequency);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Illegal argument for scan result with bssid: " + bssid, e);
+                    continue;
+                }
 
                 ScanDetail scanDetail = new ScanDetail(networkDetail, wifiSsid, bssid, flags,
                         result.signalMbm / 100, result.frequency, result.tsf, ies, null);
diff --git a/com/android/server/wifi/aware/WifiAwareClientState.java b/com/android/server/wifi/aware/WifiAwareClientState.java
index 3570f1d..987d49e 100644
--- a/com/android/server/wifi/aware/WifiAwareClientState.java
+++ b/com/android/server/wifi/aware/WifiAwareClientState.java
@@ -20,7 +20,6 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareEventCallback;
 import android.os.RemoteException;
@@ -271,47 +270,6 @@
     }
 
     /**
-     * Called on RTT success - forwards call to client.
-     */
-    public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
-        if (VDBG) {
-            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
-        }
-        try {
-            mCallback.onRangingSuccess(rangingId, results);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
-     * Called on RTT failure - forwards call to client.
-     */
-    public void onRangingFailure(int rangingId, int reason, String description) {
-        if (VDBG) {
-            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
-                    + ", description=" + description);
-        }
-        try {
-            mCallback.onRangingFailure(rangingId, reason, description);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
-     * Called on RTT operation aborted - forwards call to client.
-     */
-    public void onRangingAborted(int rangingId) {
-        if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId);
-        try {
-            mCallback.onRangingAborted(rangingId);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
      * Dump the internal state of the class.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/com/android/server/wifi/aware/WifiAwareRttStateManager.java b/com/android/server/wifi/aware/WifiAwareRttStateManager.java
deleted file mode 100644
index 9d0441f..0000000
--- a/com/android/server/wifi/aware/WifiAwareRttStateManager.java
+++ /dev/null
@@ -1,206 +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.server.wifi.aware;
-
-import android.content.Context;
-import android.net.wifi.IRttManager;
-import android.net.wifi.RttManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-
-/**
- * Manages interactions between the Aware and the RTT service. Duplicates some of the functionality
- * of the RttManager.
- */
-public class WifiAwareRttStateManager {
-    private static final String TAG = "WifiAwareRttStateMgr";
-
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    private final SparseArray<WifiAwareClientState> mPendingOperations = new SparseArray<>();
-    private AsyncChannel mAsyncChannel;
-    private Context mContext;
-
-    /**
-     * Initializes the connection to the RTT service.
-     */
-    public void start(Context context, Looper looper) {
-        if (VDBG) Log.v(TAG, "start()");
-
-        IBinder b = ServiceManager.getService(Context.WIFI_RTT_SERVICE);
-        IRttManager service = IRttManager.Stub.asInterface(b);
-        if (service == null) {
-            Log.e(TAG, "start(): not able to get WIFI_RTT_SERVICE");
-            return;
-        }
-
-        startWithRttService(context, looper, service);
-    }
-
-    /**
-     * Initializes the connection to the RTT service.
-     */
-    @VisibleForTesting
-    public void startWithRttService(Context context, Looper looper, IRttManager service) {
-        Messenger messenger;
-        try {
-            messenger = service.getMessenger(null, new int[1]);
-        } catch (RemoteException e) {
-            Log.e(TAG, "start(): not able to getMessenger() of WIFI_RTT_SERVICE");
-            return;
-        }
-
-        mAsyncChannel = new AsyncChannel();
-        mAsyncChannel.connect(context, new AwareRttHandler(looper), messenger);
-        mContext = context;
-    }
-
-    private WifiAwareClientState getAndRemovePendingOperationClient(int rangingId) {
-        WifiAwareClientState client = mPendingOperations.get(rangingId);
-        mPendingOperations.delete(rangingId);
-        return client;
-    }
-
-    /**
-     * Start a ranging operation for the client + peer MAC.
-     */
-    public void startRanging(int rangingId, WifiAwareClientState client,
-                             RttManager.RttParams[] params) {
-        if (VDBG) {
-            Log.v(TAG, "startRanging: rangingId=" + rangingId + ", parms="
-                    + Arrays.toString(params));
-        }
-
-        if (mAsyncChannel == null) {
-            Log.d(TAG, "startRanging(): AsyncChannel to RTT service not configured - failing");
-            client.onRangingFailure(rangingId, RttManager.REASON_NOT_AVAILABLE,
-                    "Aware service not able to configure connection to RTT service");
-            return;
-        }
-
-        mPendingOperations.put(rangingId, client);
-        RttManager.ParcelableRttParams pparams = new RttManager.ParcelableRttParams(params);
-        mAsyncChannel.sendMessage(RttManager.CMD_OP_START_RANGING, 0, rangingId, pparams);
-    }
-
-    private class AwareRttHandler extends Handler {
-        AwareRttHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (VDBG) Log.v(TAG, "handleMessage(): " + msg.what);
-
-            // channel configuration messages
-            switch (msg.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION,
-                                new RttManager.RttClient(mContext.getPackageName()));
-                    } else {
-                        Log.e(TAG, "Failed to set up channel connection to RTT service");
-                        mAsyncChannel = null;
-                    }
-                    return;
-                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
-                    /* NOP */
-                    return;
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    Log.e(TAG, "Channel connection to RTT service lost");
-                    mAsyncChannel = null;
-                    return;
-            }
-
-            // RTT-specific messages
-            WifiAwareClientState client = getAndRemovePendingOperationClient(msg.arg2);
-            if (client == null) {
-                Log.e(TAG, "handleMessage(): RTT message (" + msg.what
-                        + ") -- cannot find registered pending operation client for ID "
-                        + msg.arg2);
-                return;
-            }
-
-            switch (msg.what) {
-                case RttManager.CMD_OP_SUCCEEDED: {
-                    int rangingId = msg.arg2;
-                    RttManager.ParcelableRttResults results = (RttManager.ParcelableRttResults)
-                            msg.obj;
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_SUCCEEDED: rangingId=" + rangingId + ", results="
-                                + results);
-                    }
-                    for (int i = 0; i < results.mResults.length; ++i) {
-                        /*
-                         * TODO: store peer ID rather than null in the return result.
-                         */
-                        results.mResults[i].bssid = null;
-                    }
-                    client.onRangingSuccess(rangingId, results);
-                    break;
-                }
-                case RttManager.CMD_OP_FAILED: {
-                    int rangingId = msg.arg2;
-                    int reason = msg.arg1;
-                    String description = ((Bundle) msg.obj).getString(RttManager.DESCRIPTION_KEY);
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_FAILED: rangingId=" + rangingId + ", reason=" + reason
-                                + ", description=" + description);
-                    }
-                    client.onRangingFailure(rangingId, reason, description);
-                    break;
-                }
-                case RttManager.CMD_OP_ABORTED: {
-                    int rangingId = msg.arg2;
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_ABORTED: rangingId=" + rangingId);
-                    }
-                    client.onRangingAborted(rangingId);
-                    break;
-                }
-                default:
-                    Log.e(TAG, "handleMessage(): ignoring message " + msg.what);
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Dump the internal state of the class.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("WifiAwareRttStateManager:");
-        pw.println("  mPendingOperations: [" + mPendingOperations + "]");
-    }
-}
diff --git a/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/com/android/server/wifi/aware/WifiAwareServiceImpl.java
index b77ae63..421d9ac 100644
--- a/com/android/server/wifi/aware/WifiAwareServiceImpl.java
+++ b/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -16,15 +16,16 @@
 
 package com.android.server.wifi.aware;
 
+import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.DiscoverySession;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.IWifiAwareManager;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
@@ -42,7 +43,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Arrays;
+import java.util.List;
 
 /**
  * Implementation of the IWifiAwareManager AIDL interface. Performs validity
@@ -63,7 +64,6 @@
     private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
             new SparseArray<>();
     private int mNextClientId = 1;
-    private int mNextRangingId = 1;
     private final SparseIntArray mUidByClientId = new SparseIntArray();
 
     public WifiAwareServiceImpl(Context context) {
@@ -134,7 +134,7 @@
         }
 
         if (configRequest != null) {
-            enforceConnectivityInternalPermission();
+            enforceNetworkStackPermission();
         } else {
             configRequest = new ConfigRequest.Builder().build();
         }
@@ -326,7 +326,7 @@
         enforceChangePermission();
 
         if (retryCount != 0) {
-            enforceConnectivityInternalPermission();
+            enforceNetworkStackPermission();
         }
 
         if (message != null
@@ -352,30 +352,10 @@
     }
 
     @Override
-    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
-        enforceAccessPermission();
-        enforceLocationPermission();
+    public void requestMacAddresses(int uid, List peerIds, IWifiAwareMacAddressProvider callback) {
+        enforceNetworkStackPermission();
 
-        // TODO: b/35676064 restricts access to this API until decide if will open.
-        enforceConnectivityInternalPermission();
-
-        int uid = getMockableCallingUid();
-        enforceClientValidity(uid, clientId);
-        if (VDBG) {
-            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
-                    + ", parms=" + Arrays.toString(params.mParams));
-        }
-
-        if (params.mParams.length == 0) {
-            throw new IllegalArgumentException("Empty ranging parameters");
-        }
-
-        int rangingId;
-        synchronized (mLock) {
-            rangingId = mNextRangingId++;
-        }
-        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
-        return rangingId;
+        mStateManager.requestMacAddresses(uid, peerIds, callback);
     }
 
     @Override
@@ -424,8 +404,7 @@
                 TAG);
     }
 
-    private void enforceConnectivityInternalPermission() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
-                TAG);
+    private void enforceNetworkStackPermission() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_STACK, TAG);
     }
 }
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 6ced948..31bdff8 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -21,11 +21,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.net.wifi.aware.WifiAwareManager;
@@ -48,7 +48,6 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
-import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import libcore.util.HexEncoding;
@@ -62,6 +61,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -108,7 +108,6 @@
     private static final int COMMAND_TYPE_ENQUEUE_SEND_MESSAGE = 107;
     private static final int COMMAND_TYPE_ENABLE_USAGE = 108;
     private static final int COMMAND_TYPE_DISABLE_USAGE = 109;
-    private static final int COMMAND_TYPE_START_RANGING = 110;
     private static final int COMMAND_TYPE_GET_CAPABILITIES = 111;
     private static final int COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES = 112;
     private static final int COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES = 113;
@@ -167,7 +166,6 @@
     private static final String MESSAGE_BUNDLE_KEY_MAC_ADDRESS = "mac_address";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_DATA = "message_data";
     private static final String MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID = "req_instance_id";
-    private static final String MESSAGE_BUNDLE_KEY_RANGING_ID = "ranging_id";
     private static final String MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME = "message_queue_time";
     private static final String MESSAGE_BUNDLE_KEY_RETRY_COUNT = "retry_count";
     private static final String MESSAGE_BUNDLE_KEY_SUCCESS_FLAG = "success_flag";
@@ -203,7 +201,6 @@
     private volatile Capabilities mCapabilities;
     private volatile Characteristics mCharacteristics = null;
     private WifiAwareStateMachine mSm;
-    private WifiAwareRttStateManager mRtt;
     public WifiAwareDataPathStateManager mDataPathMgr;
     private PowerManager mPowerManager;
 
@@ -348,7 +345,6 @@
         mSm.setDbg(DBG);
         mSm.start();
 
-        mRtt = new WifiAwareRttStateManager();
         mDataPathMgr = new WifiAwareDataPathStateManager(this);
         mDataPathMgr.start(mContext, mSm.getHandler().getLooper(), awareMetrics,
                 permissionsWrapper);
@@ -417,6 +413,48 @@
     }
 
     /*
+     * Cross-service API: synchronized but independent of state machine
+     */
+
+    /**
+     * Translate (and return in the callback) the peerId to its MAC address representation.
+     */
+    public void requestMacAddresses(int uid, List<Integer> peerIds,
+            IWifiAwareMacAddressProvider callback) {
+        mSm.getHandler().post(() -> {
+            if (VDBG) Log.v(TAG, "requestMacAddresses: uid=" + uid + ", peerIds=" + peerIds);
+            Map<Integer, byte[]> peerIdToMacMap = new HashMap<>();
+            for (int i = 0; i < mClients.size(); ++i) {
+                WifiAwareClientState client = mClients.valueAt(i);
+                if (client.getUid() != uid) {
+                    continue;
+                }
+
+                SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
+                for (int j = 0; j < sessions.size(); ++j) {
+                    WifiAwareDiscoverySessionState session = sessions.valueAt(j);
+
+                    for (int peerId : peerIds) {
+                        WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
+                                peerId);
+                        if (peerInfo != null) {
+                            peerIdToMacMap.put(peerId, peerInfo.mMac);
+                        }
+                    }
+                }
+            }
+
+            try {
+                if (VDBG) Log.v(TAG, "requestMacAddresses: peerIdToMacMap=" + peerIdToMacMap);
+                callback.macAddress(peerIdToMacMap);
+            } catch (RemoteException e) {
+                Log.e(TAG, "requestMacAddress (sync): exception on callback -- " + e);
+
+            }
+        });
+    }
+
+    /*
      * COMMANDS
      */
 
@@ -553,20 +591,6 @@
     }
 
     /**
-     * Place a request to range a peer on the discovery session on the state machine queue.
-     */
-    public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
-                             int rangingId) {
-        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
-        msg.arg1 = COMMAND_TYPE_START_RANGING;
-        msg.arg2 = clientId;
-        msg.obj = params;
-        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
-        msg.getData().putInt(MESSAGE_BUNDLE_KEY_RANGING_ID, rangingId);
-        mSm.sendMessage(msg);
-    }
-
-    /**
      * Enable usage of Aware. Doesn't actually turn on Aware (form clusters) - that
      * only happens when a connection is created.
      */
@@ -1525,18 +1549,6 @@
                 case COMMAND_TYPE_DISABLE_USAGE:
                     waitForResponse = disableUsageLocal(mCurrentTransactionId);
                     break;
-                case COMMAND_TYPE_START_RANGING: {
-                    Bundle data = msg.getData();
-
-                    int clientId = msg.arg2;
-                    RttManager.RttParams[] params = (RttManager.RttParams[]) msg.obj;
-                    int sessionId = data.getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
-                    int rangingId = data.getInt(MESSAGE_BUNDLE_KEY_RANGING_ID);
-
-                    startRangingLocal(clientId, sessionId, params, rangingId);
-                    waitForResponse = false;
-                    break;
-                }
                 case COMMAND_TYPE_GET_CAPABILITIES:
                     if (mCapabilities == null) {
                         waitForResponse = mWifiAwareNativeApi.getCapabilities(
@@ -1614,7 +1626,6 @@
                     break;
                 case COMMAND_TYPE_DELAYED_INITIALIZATION:
                     mWifiAwareNativeManager.start();
-                    mRtt.start(mContext, mSm.getHandler().getLooper());
                     waitForResponse = false;
                     break;
                 default:
@@ -1827,9 +1838,6 @@
                 case COMMAND_TYPE_DISABLE_USAGE:
                     Log.wtf(TAG, "processTimeout: DISABLE_USAGE - shouldn't be waiting!");
                     break;
-                case COMMAND_TYPE_START_RANGING:
-                    Log.wtf(TAG, "processTimeout: START_RANGING - shouldn't be waiting!");
-                    break;
                 case COMMAND_TYPE_GET_CAPABILITIES:
                     Log.e(TAG,
                             "processTimeout: GET_CAPABILITIES timed-out - strange, will try again"
@@ -2308,49 +2316,6 @@
         return callDispatched;
     }
 
-    private void startRangingLocal(int clientId, int sessionId, RttManager.RttParams[] params,
-                                   int rangingId) {
-        if (VDBG) {
-            Log.v(TAG, "startRangingLocal: clientId=" + clientId + ", sessionId=" + sessionId
-                    + ", parms=" + Arrays.toString(params) + ", rangingId=" + rangingId);
-        }
-
-        WifiAwareClientState client = mClients.get(clientId);
-        if (client == null) {
-            Log.e(TAG, "startRangingLocal: no client exists for clientId=" + clientId);
-            return;
-        }
-
-        WifiAwareDiscoverySessionState session = client.getSession(sessionId);
-        if (session == null) {
-            Log.e(TAG, "startRangingLocal: no session exists for clientId=" + clientId
-                    + ", sessionId=" + sessionId);
-            client.onRangingFailure(rangingId, RttManager.REASON_INVALID_REQUEST,
-                    "Invalid session ID");
-            return;
-        }
-
-        for (RttManager.RttParams param : params) {
-            String peerIdStr = param.bssid;
-            try {
-                WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
-                        Integer.parseInt(peerIdStr));
-                if (peerInfo == null || peerInfo.mMac == null) {
-                    Log.d(TAG, "startRangingLocal: no MAC address for peer ID=" + peerIdStr);
-                    param.bssid = "";
-                } else {
-                    param.bssid = NativeUtil.macAddressFromByteArray(peerInfo.mMac);
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "startRangingLocal: invalid peer ID specification (in bssid field): '"
-                        + peerIdStr + "'");
-                param.bssid = "";
-            }
-        }
-
-        mRtt.startRanging(rangingId, client, params);
-    }
-
     private boolean initiateDataPathSetupLocal(short transactionId,
             WifiAwareNetworkSpecifier networkSpecifier, int peerId, int channelRequestType,
             int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
@@ -3061,7 +3026,6 @@
         }
         pw.println("  mSettableParameters: " + mSettableParameters);
         mSm.dump(fd, pw, args);
-        mRtt.dump(fd, pw, args);
         mDataPathMgr.dump(fd, pw, args);
         mWifiAwareNativeApi.dump(fd, pw, args);
     }
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
new file mode 100644
index 0000000..d30fe91
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -0,0 +1,248 @@
+/*
+ * 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.wifi.rtt;
+
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
+import android.hardware.wifi.V1_0.RttBw;
+import android.hardware.wifi.V1_0.RttConfig;
+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;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+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;
+
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiVendorHal;
+import com.android.server.wifi.util.NativeUtil;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TBD
+ */
+public class RttNative extends IWifiRttControllerEventCallback.Stub {
+    private static final String TAG = "RttNative";
+    private static final boolean VDBG = true; // STOPSHIP if true
+
+    private final RttServiceImpl mRttService;
+    private final HalDeviceManager mHalDeviceManager;
+    private final WifiVendorHal mWifiVendorHal;
+
+    private boolean mIsInitialized = false;
+
+    public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager,
+            WifiNative wifiNative) {
+        mRttService = rttService;
+        mHalDeviceManager = halDeviceManager;
+        mWifiVendorHal = wifiNative.getVendorHal();
+    }
+
+    /**
+     * Issue a range request to the HAL.
+     *
+     * @param cmdId Command ID for the request. Will be used in the corresponding
+     * {@link #onResults(int, ArrayList)}.
+     * @param request Range request.
+     * @return Success status: true for success, false for failure.
+     */
+    public boolean rangeRequest(int cmdId, RangingRequest request) {
+        if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
+        // TODO: b/65014872 replace by direct access to HalDeviceManager
+        IWifiRttController rttController = mWifiVendorHal.getRttController();
+        if (rttController == null) {
+            Log.e(TAG, "rangeRequest: RttController is null");
+            return false;
+        }
+        if (!mIsInitialized) {
+            try {
+                WifiStatus status = rttController.registerEventCallback(this);
+                if (status.code != WifiStatusCode.SUCCESS) {
+                    Log.e(TAG,
+                            "rangeRequest: cannot register event callback -- code=" + status.code);
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "rangeRequest: exception registering callback: " + e);
+                return false;
+            }
+            mIsInitialized = true;
+        }
+
+        ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request);
+        if (rttConfig == null) {
+            Log.e(TAG, "rangeRequest: invalid request parameters");
+            return false;
+        }
+
+        try {
+            WifiStatus status = rttController.rangeRequest(cmdId, rttConfig);
+            if (status.code != WifiStatusCode.SUCCESS) {
+                Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code);
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "rangeRequest: exception issuing range request: " + e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Callback from HAL with range results.
+     *
+     * @param cmdId Command ID specified in the original request
+     * {@link #rangeRequest(int, RangingRequest)}.
+     * @param halResults A list of range results.
+     */
+    @Override
+    public void onResults(int cmdId, ArrayList<RttResult> halResults) {
+        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);
+    }
+
+    private static ArrayList<RttConfig> convertRangingRequestToRttConfigs(RangingRequest request) {
+        ArrayList<RttConfig> rttConfigs = new ArrayList<>(request.mRttPeers.size());
+
+        // 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) {
+            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];
+                }
+
+                try {
+                    config.type =
+                            scanResult.is80211mcResponder() ? RttType.TWO_SIDED : RttType.ONE_SIDED;
+                    config.peer = RttPeerType.AP;
+                    config.channel.width = halChannelWidthFromScanResult(
+                            scanResult.channelWidth);
+                    config.channel.centerFreq = scanResult.frequency;
+                    if (scanResult.centerFreq0 > 0) {
+                        config.channel.centerFreq0 = scanResult.centerFreq0;
+                    }
+                    if (scanResult.centerFreq1 > 0) {
+                        config.channel.centerFreq1 = scanResult.centerFreq1;
+                    }
+                    config.burstPeriod = 0;
+                    config.numBurst = 0;
+                    config.numFramesPerBurst = 8;
+                    config.numRetriesPerRttFrame = 0;
+                    config.numRetriesPerFtmr = 0;
+                    config.mustRequestLci = false;
+                    config.mustRequestLcr = false;
+                    config.burstDuration = 15;
+                    if (config.channel.centerFreq > 5000) {
+                        config.preamble = RttPreamble.VHT;
+                    } else {
+                        config.preamble = RttPreamble.HT;
+                    }
+                    config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Invalid configuration: " + e.getMessage());
+                    continue;
+                }
+
+                rttConfigs.add(config);
+            } else {
+                Log.e(TAG, "convertRangingRequestToRttConfigs: unknown request type -- "
+                        + peer.getClass().getCanonicalName());
+                return null;
+            }
+        }
+
+
+        return rttConfigs;
+    }
+
+    static int halChannelWidthFromScanResult(int scanResultChannelWidth) {
+        switch (scanResultChannelWidth) {
+            case ScanResult.CHANNEL_WIDTH_20MHZ:
+                return WifiChannelWidthInMhz.WIDTH_20;
+            case ScanResult.CHANNEL_WIDTH_40MHZ:
+                return WifiChannelWidthInMhz.WIDTH_40;
+            case ScanResult.CHANNEL_WIDTH_80MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80;
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                return WifiChannelWidthInMhz.WIDTH_160;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80P80;
+            default:
+                throw new IllegalArgumentException(
+                        "halChannelWidthFromScanResult: bad " + scanResultChannelWidth);
+        }
+    }
+
+    static int halChannelBandwidthFromScanResult(int scanResultChannelWidth) {
+        switch (scanResultChannelWidth) {
+            case ScanResult.CHANNEL_WIDTH_20MHZ:
+                return RttBw.BW_20MHZ;
+            case ScanResult.CHANNEL_WIDTH_40MHZ:
+                return RttBw.BW_40MHZ;
+            case ScanResult.CHANNEL_WIDTH_80MHZ:
+                return RttBw.BW_80MHZ;
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                return RttBw.BW_160MHZ;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return RttBw.BW_160MHZ;
+            default:
+                throw new IllegalArgumentException(
+                        "halChannelBandwidthFromScanResult: bad " + scanResultChannelWidth);
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("RttNative:");
+        pw.println("  mIsInitialized: " + mIsInitialized);
+    }
+}
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
new file mode 100644
index 0000000..71e8a0a
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -0,0 +1,67 @@
+/*
+ * 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.wifi.rtt;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.server.SystemService;
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+/**
+ * TBD.
+ */
+public class RttService extends SystemService {
+    private static final String TAG = "RttService";
+    private RttServiceImpl mImpl;
+
+    public RttService(Context context) {
+        super(context);
+        mImpl = new RttServiceImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        Log.i(TAG, "Registering " + Context.WIFI_RTT2_SERVICE);
+        publishBinderService(Context.WIFI_RTT2_SERVICE, mImpl);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            Log.i(TAG, "Starting " + Context.WIFI_RTT2_SERVICE);
+
+            WifiInjector wifiInjector = WifiInjector.getInstance();
+            if (wifiInjector == null) {
+                Log.e(TAG, "onBootPhase(PHASE_SYSTEM_SERVICES_READY): NULL injector!");
+                return;
+            }
+
+            HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager();
+            HandlerThread handlerThread = wifiInjector.getRttHandlerThread();
+            WifiNative wifiNative = wifiInjector.getWifiNative();
+            WifiPermissionsUtil wifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
+
+            RttNative rttNative = new RttNative(mImpl, halDeviceManager, wifiNative);
+            mImpl.start(handlerThread.getLooper(), rttNative, wifiPermissionsUtil);
+        }
+    }
+}
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
new file mode 100644
index 0000000..d248768
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -0,0 +1,399 @@
+/*
+ * 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.wifi.rtt;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+/**
+ * Implementation of the IWifiRttManager AIDL interface and of the RttService state manager.
+ */
+public class RttServiceImpl extends IWifiRttManager.Stub {
+    private static final String TAG = "RttServiceImpl";
+    private static final boolean VDBG = true; // STOPSHIP if true
+
+    private final Context mContext;
+    private WifiPermissionsUtil mWifiPermissionsUtil;
+
+    private RttServiceSynchronized mRttServiceSynchronized;
+
+
+    public RttServiceImpl(Context context) {
+        mContext = context;
+    }
+
+    /*
+     * INITIALIZATION
+     */
+
+    /**
+     * Initializes the RTT service (usually with objects from an injector).
+     *
+     * @param looper The looper on which to synchronize operations.
+     * @param rttNative The Native interface to the HAL.
+     * @param wifiPermissionsUtil Utility for permission checks.
+     */
+    public void start(Looper looper, RttNative rttNative, WifiPermissionsUtil wifiPermissionsUtil) {
+        mWifiPermissionsUtil = wifiPermissionsUtil;
+        mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+    }
+
+    /*
+     * ASYNCHRONOUS DOMAIN - can be called from different threads!
+     */
+
+    /**
+     * Proxy for the final native call of the parent class. Enables mocking of
+     * the function.
+     */
+    public int getMockableCallingUid() {
+        return getCallingUid();
+    }
+
+    /**
+     * Binder interface API to start a ranging operation. Called on binder thread, operations needs
+     * to be posted to handler thread.
+     */
+    @Override
+    public void startRanging(IBinder binder, String callingPackage, RangingRequest request,
+            IRttCallback callback) throws RemoteException {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: binder=" + binder + ", callingPackage=" + callingPackage
+                    + ", request=" + request + ", callback=" + callback);
+        }
+        // verify arguments
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
+        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();
+
+        final int uid = getMockableCallingUid();
+
+        // permission check
+        enforceAccessPermission();
+        enforceChangePermission();
+        enforceLocationPermission(callingPackage, uid);
+
+        // register for binder death
+        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                if (VDBG) Log.v(TAG, "binderDied: uid=" + uid);
+                binder.unlinkToDeath(this, 0);
+
+                mRttServiceSynchronized.mHandler.post(() -> {
+                    mRttServiceSynchronized.cleanUpOnClientDeath(uid);
+                });
+            }
+        };
+
+        try {
+            binder.linkToDeath(dr, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error on linkToDeath - " + e);
+        }
+
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.queueRangingRequest(uid, binder, dr, callingPackage, request,
+                    callback);
+        });
+    }
+
+    /**
+     * 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) {
+        if (VDBG) Log.v(TAG, "onRangingResults: cmdId=" + cmdId);
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.onRangingResults(cmdId, results);
+        });
+    }
+
+    private void enforceAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
+    }
+
+    private void enforceChangePermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
+    }
+
+    private void enforceLocationPermission(String callingPackage, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
+                TAG);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump RttService from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+        pw.println("Wi-Fi RTT Service");
+        mRttServiceSynchronized.dump(fd, pw, args);
+    }
+
+    /*
+     * SYNCHRONIZED DOMAIN
+     */
+
+    /**
+     * RTT service implementation - synchronized on a single thread. All commands should be posted
+     * to the exposed handler.
+     */
+    private class RttServiceSynchronized {
+        public Handler mHandler;
+
+        private RttNative mRttNative;
+        private int mNextCommandId = 1000;
+        private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
+
+        RttServiceSynchronized(Looper looper, RttNative rttNative) {
+            mRttNative = rttNative;
+
+            mHandler = new Handler(looper);
+        }
+
+        private void cleanUpOnClientDeath(int uid) {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+                        + ", mRttRequestQueue=" + mRttRequestQueue);
+            }
+            ListIterator<RttRequestInfo> it = mRttRequestQueue.listIterator();
+            while (it.hasNext()) {
+                RttRequestInfo rri = it.next();
+                if (rri.uid == uid) {
+                    // TODO: actually abort operation - though API is not clear or clean
+                    if (rri.cmdId == 0) {
+                        // Until that happens we will get results for the last operation: which is
+                        // why we don't dispatch a new range request off the queue and keep the
+                        // currently running operation in the queue
+                        it.remove();
+                    }
+                }
+            }
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+                        + ", after cleanup - mRttRequestQueue=" + mRttRequestQueue);
+            }
+        }
+
+        private void queueRangingRequest(int uid, IBinder binder, IBinder.DeathRecipient dr,
+                String callingPackage, RangingRequest request, IRttCallback callback) {
+            RttRequestInfo newRequest = new RttRequestInfo();
+            newRequest.uid = uid;
+            newRequest.binder = binder;
+            newRequest.dr = dr;
+            newRequest.callingPackage = callingPackage;
+            newRequest.request = request;
+            newRequest.callback = callback;
+            mRttRequestQueue.add(newRequest);
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.queueRangingRequest: newRequest=" + newRequest);
+            }
+
+            executeNextRangingRequestIfPossible();
+        }
+
+        private void executeNextRangingRequestIfPossible() {
+            if (mRttRequestQueue.size() == 0) {
+                if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: no requests pending");
+                return;
+            }
+
+            RttRequestInfo nextRequest = mRttRequestQueue.get(0);
+            if (nextRequest.cmdId != 0) {
+                if (VDBG) {
+                    Log.v(TAG, "executeNextRangingRequestIfPossible: called but a command is "
+                            + "executing. topOfQueue=" + nextRequest);
+                }
+                return;
+            }
+
+            nextRequest.cmdId = mNextCommandId++;
+            startRanging(nextRequest);
+        }
+
+        private void startRanging(RttRequestInfo nextRequest) {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
+            }
+
+            if (!mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
+                Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
+                try {
+                    nextRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: HAL request failed, callback "
+                            + "failed -- " + e);
+                }
+
+                mRttRequestQueue.remove(0);
+                executeNextRangingRequestIfPossible();
+            }
+        }
+
+        private void onRangingResults(int cmdId, List<RangingResult> results) {
+            if (mRttRequestQueue.size() == 0) {
+                Log.e(TAG, "RttServiceSynchronized.onRangingResults: no current RTT request "
+                        + "pending!?");
+                return;
+            }
+            RttRequestInfo topOfQueueRequest = mRttRequestQueue.get(0);
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+                        + ", topOfQueueRequest=" + topOfQueueRequest + ", results="
+                        + Arrays.toString(results.toArray()));
+            }
+
+            if (topOfQueueRequest.cmdId != cmdId) {
+                Log.e(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+                        + ", does not match pending RTT request cmdId=" + topOfQueueRequest.cmdId);
+                return;
+            }
+
+            boolean permissionGranted = mWifiPermissionsUtil.checkCallersLocationPermission(
+                    topOfQueueRequest.callingPackage, topOfQueueRequest.uid);
+            try {
+                if (permissionGranted) {
+                    addMissingEntries(topOfQueueRequest.request, results);
+                    topOfQueueRequest.callback.onRangingResults(
+                            RangingResultCallback.STATUS_SUCCESS, results);
+                } else {
+                    Log.w(TAG, "RttServiceSynchronized.onRangingResults: location permission "
+                            + "revoked - not forwarding results");
+                    topOfQueueRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL,
+                            null);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG,
+                        "RttServiceSynchronized.onRangingResults: callback exception -- " + e);
+            }
+
+            // clean-up binder death listener: the callback for results is a onetime event - now
+            // done with the binder.
+            topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
+
+            mRttRequestQueue.remove(0);
+            executeNextRangingRequestIfPossible();
+        }
+
+        /*
+         * Make sure the results contain an entry for each request. Add results with FAIL status
+         * if missing.
+         */
+        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())));
+            }
+
+            for (RangingRequest.RttPeer peer: request.mRttPeers) {
+                byte[] addr;
+                if (peer instanceof RangingRequest.RttPeerAp) {
+                    addr = NativeUtil.macAddressToByteArray(
+                            ((RangingRequest.RttPeerAp) peer).scanResult.BSSID);
+                } else {
+                    continue;
+                }
+                String canonicString = new String(HexEncoding.encode(addr));
+
+                if (!resultEntries.contains(canonicString)) {
+                    if (VDBG) {
+                        Log.v(TAG, "padRangingResultsWithMissingResults: missing=" + canonicString);
+                    }
+                    results.add(new RangingResult(RangingResultCallback.STATUS_FAIL, addr, 0, 0, 0,
+                            0));
+                }
+            }
+        }
+
+        // dump call (asynchronous most likely)
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("  mNextCommandId: " + mNextCommandId);
+            pw.println("  mRttRequestQueue: " + mRttRequestQueue);
+            mRttNative.dump(fd, pw, args);
+        }
+    }
+
+    private static class RttRequestInfo {
+        public int uid;
+        public IBinder binder;
+        public IBinder.DeathRecipient dr;
+        public String callingPackage;
+        public RangingRequest request;
+        public byte[] mac;
+        public IRttCallback callback;
+
+        public int cmdId = 0; // uninitialized cmdId value
+
+        @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();
+        }
+    }
+}
diff --git a/com/android/server/wifi/scanner/ChannelHelper.java b/com/android/server/wifi/scanner/ChannelHelper.java
index d87df07..6a01f0c 100644
--- a/com/android/server/wifi/scanner/ChannelHelper.java
+++ b/com/android/server/wifi/scanner/ChannelHelper.java
@@ -242,7 +242,7 @@
         if (scanSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
             return toString(scanSettings.channels);
         } else {
-            return toString(scanSettings.band);
+            return bandToString(scanSettings.band);
         }
     }
 
@@ -255,7 +255,7 @@
         if (bucketSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
             return toString(bucketSettings.channels, bucketSettings.num_channels);
         } else {
-            return toString(bucketSettings.band);
+            return bandToString(bucketSettings.band);
         }
     }
 
@@ -293,7 +293,10 @@
         return sb.toString();
     }
 
-    private static String toString(int band) {
+    /**
+     * Converts a WifiScanner.WIFI_BAND_* constant to a meaningful String
+     */
+    public static String bandToString(int band) {
         switch (band) {
             case WifiScanner.WIFI_BAND_UNSPECIFIED:
                 return "unspecified";
@@ -310,7 +313,6 @@
             case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
                 return "24Ghz & 5Ghz (DFS incl)";
         }
-
         return "invalid band";
     }
 }
diff --git a/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 7d0ccba..890f72e 100644
--- a/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -131,11 +131,6 @@
     }
 
     @Override
-    public boolean shouldScheduleBackgroundScanForHwPno() {
-        return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
-    }
-
-    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mWificondScannerDelegate.dump(fd, pw, args);
     }
diff --git a/com/android/server/wifi/scanner/WifiScannerImpl.java b/com/android/server/wifi/scanner/WifiScannerImpl.java
index 5281b3a..dacb007 100644
--- a/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -155,11 +155,5 @@
      */
     public abstract boolean isHwPnoSupported(boolean isConnectedPno);
 
-    /**
-     * This returns whether a background scan should be running for HW PNO scan or not.
-     * @return true if background scan needs to be started, false otherwise.
-     */
-    public abstract boolean shouldScheduleBackgroundScanForHwPno();
-
     protected abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 4b8e284..02462f6 100644
--- a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -75,7 +75,6 @@
     private static final String TAG = WifiScanningService.TAG;
     private static final boolean DBG = false;
 
-    private static final int MIN_PERIOD_PER_CHANNEL_MS = 200;               // DFS needs 120 ms
     private static final int UNKNOWN_PID = -1;
 
     private final LocalLog mLocalLog = new LocalLog(512);
@@ -240,10 +239,6 @@
 
     private static final int CMD_SCAN_RESULTS_AVAILABLE              = BASE + 0;
     private static final int CMD_FULL_SCAN_RESULTS                   = BASE + 1;
-    private static final int CMD_HOTLIST_AP_FOUND                    = BASE + 2;
-    private static final int CMD_HOTLIST_AP_LOST                     = BASE + 3;
-    private static final int CMD_WIFI_CHANGE_DETECTED                = BASE + 4;
-    private static final int CMD_WIFI_CHANGE_TIMEOUT                 = BASE + 5;
     private static final int CMD_DRIVER_LOADED                       = BASE + 6;
     private static final int CMD_DRIVER_UNLOADED                     = BASE + 7;
     private static final int CMD_SCAN_PAUSED                         = BASE + 8;
@@ -1724,10 +1719,7 @@
             }
             logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
             addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
-            // HW PNO is supported, check if we need a background scan running for this.
-            if (mScannerImpl.shouldScheduleBackgroundScanForHwPno()) {
-                addBackgroundScanRequest(scanSettings);
-            }
+
             return true;
         }
 
@@ -2213,7 +2205,7 @@
 
     static String describeTo(StringBuilder sb, ScanSettings scanSettings) {
         sb.append("ScanSettings { ")
-          .append(" band:").append(scanSettings.band)
+          .append(" band:").append(ChannelHelper.bandToString(scanSettings.band))
           .append(" period:").append(scanSettings.periodInMs)
           .append(" reportEvents:").append(scanSettings.reportEvents)
           .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan)
diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java
index 12a0bde..fb878e6 100644
--- a/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -34,7 +34,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -50,7 +49,6 @@
     private static final String TAG = "WificondScannerImpl";
     private static final boolean DBG = false;
 
-    public static final String BACKGROUND_PERIOD_ALARM_TAG = TAG + " Background Scan Period";
     public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
     // Max number of networks that can be specified to wificond per scan request
     public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
@@ -69,20 +67,9 @@
     private final Object mSettingsLock = new Object();
 
     // Next scan settings to apply when the previous scan completes
-    private WifiNative.ScanSettings mPendingBackgroundScanSettings = null;
-    private WifiNative.ScanEventHandler mPendingBackgroundScanEventHandler = null;
     private WifiNative.ScanSettings mPendingSingleScanSettings = null;
     private WifiNative.ScanEventHandler mPendingSingleScanEventHandler = null;
 
-    // Active background scan settings/state
-    private WifiNative.ScanSettings mBackgroundScanSettings = null;
-    private WifiNative.ScanEventHandler mBackgroundScanEventHandler = null;
-    private int mNextBackgroundScanPeriod = 0;
-    private int mNextBackgroundScanId = 0;
-    private boolean mBackgroundScanPeriodPending = false;
-    private boolean mBackgroundScanPaused = false;
-    private ScanBuffer mBackgroundScanBuffer = new ScanBuffer(SCAN_BUFFER_CAPACITY);
-
     private ArrayList<ScanDetail> mNativeScanResults;
     private WifiScanner.ScanData mLatestSingleScanResult =
             new WifiScanner.ScanData(0, 0, new ScanResult[0]);
@@ -110,14 +97,6 @@
      */
     private static final long SCAN_TIMEOUT_MS = 15000;
 
-    AlarmManager.OnAlarmListener mScanPeriodListener = new AlarmManager.OnAlarmListener() {
-            public void onAlarm() {
-                synchronized (mSettingsLock) {
-                    handleScanPeriod();
-                }
-            }
-        };
-
     AlarmManager.OnAlarmListener mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
             public void onAlarm() {
                 synchronized (mSettingsLock) {
@@ -161,7 +140,6 @@
             mPendingSingleScanSettings = null;
             mPendingSingleScanEventHandler = null;
             stopHwPnoScan();
-            stopBatchedScan();
             mLastScanSettings = null; // finally clear any active scan
         }
     }
@@ -210,111 +188,23 @@
     @Override
     public boolean startBatchedScan(WifiNative.ScanSettings settings,
             WifiNative.ScanEventHandler eventHandler) {
-        if (settings == null || eventHandler == null) {
-            Log.w(TAG, "Invalid arguments for startBatched: settings=" + settings
-                    + ",eventHandler=" + eventHandler);
-            return false;
-        }
-
-        if (settings.max_ap_per_scan < 0 || settings.max_ap_per_scan > MAX_APS_PER_SCAN) {
-            return false;
-        }
-        if (settings.num_buckets < 0 || settings.num_buckets > MAX_SCAN_BUCKETS) {
-            return false;
-        }
-        if (settings.report_threshold_num_scans < 0
-                || settings.report_threshold_num_scans > SCAN_BUFFER_CAPACITY) {
-            return false;
-        }
-        if (settings.report_threshold_percent < 0 || settings.report_threshold_percent > 100) {
-            return false;
-        }
-        if (settings.base_period_ms <= 0) {
-            return false;
-        }
-        for (int i = 0; i < settings.num_buckets; ++i) {
-            WifiNative.BucketSettings bucket = settings.buckets[i];
-            if (bucket.period_ms % settings.base_period_ms != 0) {
-                return false;
-            }
-        }
-
-        synchronized (mSettingsLock) {
-            stopBatchedScan();
-            if (DBG) {
-                Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period="
-                        + settings.base_period_ms + " ms");
-            }
-            mPendingBackgroundScanSettings = settings;
-            mPendingBackgroundScanEventHandler = eventHandler;
-            handleScanPeriod(); // Try to start scan immediately
-            return true;
-        }
+        Log.w(TAG, "startBatchedScan() is not supported");
+        return false;
     }
 
     @Override
     public void stopBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Stopping scan");
-            mBackgroundScanSettings = null;
-            mBackgroundScanEventHandler = null;
-            mPendingBackgroundScanSettings = null;
-            mPendingBackgroundScanEventHandler = null;
-            mBackgroundScanPaused = false;
-            mBackgroundScanPeriodPending = false;
-            unscheduleScansLocked();
-        }
-        processPendingScans();
+        Log.w(TAG, "stopBatchedScan() is not supported");
     }
 
     @Override
     public void pauseBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Pausing scan");
-            // if there isn't a pending scan then make the current scan pending
-            if (mPendingBackgroundScanSettings == null) {
-                mPendingBackgroundScanSettings = mBackgroundScanSettings;
-                mPendingBackgroundScanEventHandler = mBackgroundScanEventHandler;
-            }
-            mBackgroundScanSettings = null;
-            mBackgroundScanEventHandler = null;
-            mBackgroundScanPeriodPending = false;
-            mBackgroundScanPaused = true;
-
-            unscheduleScansLocked();
-
-            WifiScanner.ScanData[] results = getLatestBatchedScanResults(/* flush = */ true);
-            if (mPendingBackgroundScanEventHandler != null) {
-                mPendingBackgroundScanEventHandler.onScanPaused(results);
-            }
-        }
-        processPendingScans();
+        Log.w(TAG, "pauseBatchedScan() is not supported");
     }
 
     @Override
     public void restartBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Restarting scan");
-            if (mPendingBackgroundScanEventHandler != null) {
-                mPendingBackgroundScanEventHandler.onScanRestarted();
-            }
-            mBackgroundScanPaused = false;
-            handleScanPeriod();
-        }
-    }
-
-    private void unscheduleScansLocked() {
-        mAlarmManager.cancel(mScanPeriodListener);
-        if (mLastScanSettings != null) {
-            mLastScanSettings.backgroundScanActive = false;
-        }
-    }
-
-    private void handleScanPeriod() {
-        synchronized (mSettingsLock) {
-            mBackgroundScanPeriodPending = true;
-            processPendingScans();
-        }
+        Log.w(TAG, "restartBatchedScan() is not supported");
     }
 
     private void handleScanTimeout() {
@@ -342,56 +232,6 @@
             final LastScanSettings newScanSettings =
                     new LastScanSettings(mClock.getElapsedSinceBootMillis());
 
-            // Update scan settings if there is a pending scan
-            if (!mBackgroundScanPaused) {
-                if (mPendingBackgroundScanSettings != null) {
-                    mBackgroundScanSettings = mPendingBackgroundScanSettings;
-                    mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
-                    mNextBackgroundScanPeriod = 0;
-                    mPendingBackgroundScanSettings = null;
-                    mPendingBackgroundScanEventHandler = null;
-                    mBackgroundScanPeriodPending = true;
-                }
-                if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
-                    int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
-                    for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
-                            ++bucket_id) {
-                        WifiNative.BucketSettings bucket =
-                                mBackgroundScanSettings.buckets[bucket_id];
-                        if (mNextBackgroundScanPeriod % (bucket.period_ms
-                                        / mBackgroundScanSettings.base_period_ms) == 0) {
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
-                                reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
-                            }
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
-                                reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
-                            }
-                            // only no batch if all buckets specify it
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
-                                reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
-                            }
-
-                            allFreqs.addChannels(bucket);
-                        }
-                    }
-                    if (!allFreqs.isEmpty()) {
-                        newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
-                                mBackgroundScanSettings.max_ap_per_scan, reportEvents,
-                                mBackgroundScanSettings.report_threshold_num_scans,
-                                mBackgroundScanSettings.report_threshold_percent);
-                    }
-                    mNextBackgroundScanPeriod++;
-                    mBackgroundScanPeriodPending = false;
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.getElapsedSinceBootMillis()
-                                    + mBackgroundScanSettings.base_period_ms,
-                            BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
-                }
-            }
-
             if (mPendingSingleScanSettings != null) {
                 boolean reportFullResults = false;
                 ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
@@ -422,16 +262,26 @@
                 mPendingSingleScanEventHandler = null;
             }
 
-            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
-                    && !allFreqs.isEmpty()) {
-                pauseHwPnoScan();
-                Set<Integer> freqs = allFreqs.getScanFreqs();
-                boolean success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+            if (newScanSettings.singleScanActive) {
+                boolean success = false;
+                Set<Integer> freqs;
+                if (!allFreqs.isEmpty()) {
+                    pauseHwPnoScan();
+                    freqs = allFreqs.getScanFreqs();
+                    success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+                    if (!success) {
+                        Log.e(TAG, "Failed to start scan, freqs=" + freqs);
+                    }
+                } else {
+                    // There is a scan request but no available channels could be scanned for.
+                    // We regard it as a scan failure in this case.
+                    Log.e(TAG, "Failed to start scan because there is "
+                            + "no available channel to scan for");
+                }
                 if (success) {
                     // TODO handle scan timeout
                     if (DBG) {
                         Log.d(TAG, "Starting wifi scan for freqs=" + freqs
-                                + ", background=" + newScanSettings.backgroundScanActive
                                 + ", single=" + newScanSettings.singleScanActive);
                     }
                     mLastScanSettings = newScanSettings;
@@ -439,7 +289,6 @@
                             mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
                             TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
                 } else {
-                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
                     // indicate scan failure async
                     mEventHandler.post(new Runnable() {
                             public void run() {
@@ -449,7 +298,6 @@
                                 }
                             }
                         });
-                    // TODO(b/27769665) background scans should be failed too if scans fail enough
                 }
             } else if (isHwPnoScanRequired()) {
                 newScanSettings.setHwPnoScan(mPnoSettings.networkList, mPnoEventHandler);
@@ -512,7 +360,6 @@
                     mLastScanSettings.singleScanEventHandler
                             .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
                 }
-                // TODO(b/27769665) background scans should be failed too if scans fail enough
                 mLastScanSettings = null;
             }
         }
@@ -597,18 +444,13 @@
                 return;
             }
 
-            if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
             mNativeScanResults = mWifiNative.getScanResults();
             List<ScanResult> singleScanResults = new ArrayList<>();
-            List<ScanResult> backgroundScanResults = new ArrayList<>();
             int numFilteredScanResults = 0;
             for (int i = 0; i < mNativeScanResults.size(); ++i) {
                 ScanResult result = mNativeScanResults.get(i).getScanResult();
                 long timestamp_ms = result.timestamp / 1000; // convert us -> ms
                 if (timestamp_ms > mLastScanSettings.startTime) {
-                    if (mLastScanSettings.backgroundScanActive) {
-                        backgroundScanResults.add(result);
-                    }
                     if (mLastScanSettings.singleScanActive
                             && mLastScanSettings.singleScanFreqs.containsChannel(
                                     result.frequency)) {
@@ -622,49 +464,6 @@
                 Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
             }
 
-            if (mLastScanSettings.backgroundScanActive) {
-                if (mBackgroundScanEventHandler != null) {
-                    if ((mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
-                        for (ScanResult scanResult : backgroundScanResults) {
-                            // TODO(b/27506257): Fill in correct bucketsScanned value
-                            mBackgroundScanEventHandler.onFullScanResult(scanResult, 0);
-                        }
-                    }
-                }
-
-                Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR);
-                ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps,
-                            backgroundScanResults.size())];
-                for (int i = 0; i < scanResultsArray.length; ++i) {
-                    scanResultsArray[i] = backgroundScanResults.get(i);
-                }
-
-                if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
-                    // TODO(b/27506257): Fill in correct bucketsScanned value
-                    mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0,
-                                    scanResultsArray));
-                }
-
-                if (mBackgroundScanEventHandler != null) {
-                    if ((mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
-                            || (mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0
-                            || (mLastScanSettings.reportEvents
-                                    == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
-                                    && (mBackgroundScanBuffer.size()
-                                            >= (mBackgroundScanBuffer.capacity()
-                                                    * mLastScanSettings.reportPercentThreshold
-                                                    / 100)
-                                            || mBackgroundScanBuffer.size()
-                                            >= mLastScanSettings.reportNumScansThreshold))) {
-                        mBackgroundScanEventHandler
-                                .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
-                    }
-                }
-            }
-
             if (mLastScanSettings.singleScanActive
                     && mLastScanSettings.singleScanEventHandler != null) {
                 if (mLastScanSettings.reportSingleScanFullResults) {
@@ -675,7 +474,7 @@
                     }
                 }
                 Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
-                mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
+                mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
                         isAllChannelsScanned(mLastScanSettings.singleScanFreqs),
                         singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
                 mLastScanSettings.singleScanEventHandler
@@ -689,13 +488,7 @@
 
     @Override
     public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
-        synchronized (mSettingsLock) {
-            WifiScanner.ScanData[] results = mBackgroundScanBuffer.get();
-            if (flush) {
-                mBackgroundScanBuffer.clear();
-            }
-            return results;
-        }
+        return null;
     }
 
     private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
@@ -768,11 +561,6 @@
     }
 
     @Override
-    public boolean shouldScheduleBackgroundScanForHwPno() {
-        return false;
-    }
-
-    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mSettingsLock) {
             pw.println("Latest native scan results:");
@@ -813,24 +601,6 @@
             this.startTime = startTime;
         }
 
-        // Background settings
-        public boolean backgroundScanActive = false;
-        public int scanId;
-        public int maxAps;
-        public int reportEvents;
-        public int reportNumScansThreshold;
-        public int reportPercentThreshold;
-
-        public void setBackgroundScan(int scanId, int maxAps, int reportEvents,
-                int reportNumScansThreshold, int reportPercentThreshold) {
-            this.backgroundScanActive = true;
-            this.scanId = scanId;
-            this.maxAps = maxAps;
-            this.reportEvents = reportEvents;
-            this.reportNumScansThreshold = reportNumScansThreshold;
-            this.reportPercentThreshold = reportPercentThreshold;
-        }
-
         // Single scan settings
         public boolean singleScanActive = false;
         public boolean reportSingleScanFullResults;
@@ -859,44 +629,6 @@
         }
     }
 
-
-    private static class ScanBuffer {
-        private final ArrayDeque<WifiScanner.ScanData> mBuffer;
-        private int mCapacity;
-
-        ScanBuffer(int capacity) {
-            mCapacity = capacity;
-            mBuffer = new ArrayDeque<>(mCapacity);
-        }
-
-        public int size() {
-            return mBuffer.size();
-        }
-
-        public int capacity() {
-            return mCapacity;
-        }
-
-        public boolean isFull() {
-            return size() == mCapacity;
-        }
-
-        public void add(WifiScanner.ScanData scanData) {
-            if (isFull()) {
-                mBuffer.pollFirst();
-            }
-            mBuffer.offerLast(scanData);
-        }
-
-        public void clear() {
-            mBuffer.clear();
-        }
-
-        public WifiScanner.ScanData[] get() {
-            return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]);
-        }
-    }
-
     /**
      * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
      * state too often which is not handled very well by some drivers.
diff --git a/com/android/server/wifi/util/KalmanFilter.java b/com/android/server/wifi/util/KalmanFilter.java
new file mode 100644
index 0000000..b961ed8
--- /dev/null
+++ b/com/android/server/wifi/util/KalmanFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.wifi.util;
+
+/**
+ * Utility providiing a basic Kalman filter
+ *
+ * For background, see https://en.wikipedia.org/wiki/Kalman_filter
+ */
+public class KalmanFilter {
+    public Matrix mF; // stateTransition
+    public Matrix mQ; // processNoiseCovariance
+    public Matrix mH; // observationModel
+    public Matrix mR; // observationNoiseCovariance
+    public Matrix mP; // aPosterioriErrorCovariance
+    public Matrix mx; // stateEstimate
+
+    /**
+     * Performs the prediction phase of the filter, using the state estimate to produce
+     * a new estimate for the current timestep.
+     */
+    public void predict() {
+        mx = mF.dot(mx);
+        mP = mF.dot(mP).dotTranspose(mF).plus(mQ);
+    }
+
+    /**
+     * Updates the state estimate to incorporate the new observation z.
+     */
+    public void update(Matrix z) {
+        Matrix y = z.minus(mH.dot(mx));
+        Matrix tS = mH.dot(mP).dotTranspose(mH).plus(mR);
+        Matrix tK = mP.dotTranspose(mH).dot(tS.inverse());
+        mx = mx.plus(tK.dot(y));
+        mP = mP.minus(tK.dot(mH).dot(mP));
+    }
+
+    @Override
+    public String toString() {
+        return "{F: " + mF
+                + " Q: " + mQ
+                + " H: " + mH
+                + " R: " + mR
+                + " P: " + mP
+                + " x: " + mx
+                + "}";
+    }
+}
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
index c76b905..5365e27 100644
--- a/com/android/server/wm/AppWindowAnimator.java
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -16,10 +16,10 @@
 
 package com.android.server.wm;
 
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -78,11 +78,8 @@
     // requires that the duration of the two animations are the same.
     SurfaceControl thumbnail;
     int thumbnailTransactionSeq;
-    // TODO(b/62029108): combine both members into a private one. Create a member function to set
-    // the thumbnail layer to +1 to the highest layer position and replace all setter instances
-    // with this function. Remove all unnecessary calls to both variables in other classes.
-    int thumbnailLayer;
-    int thumbnailForceAboveLayer;
+    private int mThumbnailLayer;
+
     Animation thumbnailAnimation;
     final Transformation thumbnailTransformation = new Transformation();
     // This flag indicates that the destruction of the thumbnail surface is synchronized with
@@ -256,7 +253,7 @@
 
     private void updateLayers() {
         mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-        thumbnailLayer = mAppToken.getHighestAnimLayer();
+        updateThumbnailLayer();
     }
 
     private void stepThumbnailAnimation(long currentTime) {
@@ -280,26 +277,35 @@
         thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
         if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
                 "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
-                + " layer=" + thumbnailLayer
+                + " layer=" + mThumbnailLayer
                 + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
                 + "," + tmpFloats[Matrix.MSKEW_Y]
                 + "][" + tmpFloats[Matrix.MSKEW_X]
                 + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
         thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        if (thumbnailForceAboveLayer > 0) {
-            thumbnail.setLayer(thumbnailForceAboveLayer + 1);
-        } else {
-            // The thumbnail is layered below the window immediately above this
-            // token's anim layer.
-            thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
-                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
-        }
+        updateThumbnailLayer();
         thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
                 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
         thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
     }
 
     /**
+     * Updates the thumbnail layer z order to just above the highest animation layer if changed
+     */
+    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;
+            }
+        }
+    }
+
+    /**
      * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
      * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
      * and keep producing the first frame of the animation.
@@ -473,7 +479,7 @@
         }
         if (thumbnail != null) {
             pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
-                    pw.print(" layer="); pw.println(thumbnailLayer);
+                    pw.print(" layer="); pw.println(mThumbnailLayer);
             pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
             pw.print(prefix); pw.print("thumbnailTransformation=");
                     pw.println(thumbnailTransformation.toShortString());
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index ea2f305..a1eeff8 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -16,8 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -53,6 +52,7 @@
 import static com.android.server.wm.proto.AppWindowTokenProto.NAME;
 import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.content.res.Configuration;
@@ -1170,7 +1170,6 @@
                 wAppAnimator.thumbnail.destroy();
             }
             wAppAnimator.thumbnail = tAppAnimator.thumbnail;
-            wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
             wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
             tAppAnimator.thumbnail = null;
         }
@@ -1311,7 +1310,8 @@
 
                 // 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.getStackById(PINNED_STACK_ID);
+                final TaskStack pinnedStack =
+                        mDisplayContent.getStack(WINDOWING_MODE_PINNED);
                 if (pinnedStack != null) {
                     pinnedStack.onAllWindowsDrawn();
                 }
@@ -1620,8 +1620,9 @@
         }
     }
 
+    @CallSuper
     @Override
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         writeNameToProto(proto, NAME);
         super.writeToProto(proto, WINDOW_TOKEN);
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index 28ba9b3..9e028d3 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -21,12 +21,19 @@
 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_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.app.WindowConfiguration.activityTypeToString;
+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;
 
+import android.annotation.CallSuper;
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.ArrayList;
 
@@ -147,6 +154,17 @@
         onOverrideConfigurationChanged(mTmpConfig);
     }
 
+    /**
+     * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
+     * with another activity.
+     */
+    public boolean inMultiWindowMode() {
+        /*@WindowConfiguration.WindowingMode*/ int windowingMode =
+                mFullConfiguration.windowConfiguration.getWindowingMode();
+        return windowingMode != WINDOWING_MODE_FULLSCREEN
+                && windowingMode != WINDOWING_MODE_UNDEFINED;
+    }
+
     /** Returns true if this container is currently in split-screen windowing mode. */
     public boolean inSplitScreenWindowingMode() {
         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
@@ -170,7 +188,7 @@
      * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
      * its current state.
      */
-    public boolean supportSplitScreenWindowingMode() {
+    public boolean supportsSplitScreenWindowingMode() {
         return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
     }
 
@@ -220,9 +238,48 @@
         /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType();
         /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType();
 
-        return thisType == otherType
-                || thisType == ACTIVITY_TYPE_UNDEFINED
-                || otherType == ACTIVITY_TYPE_UNDEFINED;
+        if (thisType == otherType) {
+            return true;
+        }
+        if (thisType == ACTIVITY_TYPE_ASSISTANT) {
+            // Assistant activities are only compatible with themselves...
+            return false;
+        }
+        // Otherwise we are compatible if us or other is not currently defined.
+        return thisType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    /**
+     * Returns true if this container is compatible with the input windowing mode and activity type.
+     * The container is compatible:
+     * - If {@param activityType} and {@param windowingMode} match this container activity type and
+     * windowing mode.
+     * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
+     * standard or undefined and its windowing mode matches {@param windowingMode}.
+     * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
+     * also standard or undefined and its activity type matches {@param activityType} regardless of
+     * if {@param windowingMode} matches the containers windowing mode.
+     */
+    public boolean isCompatible(int windowingMode, int activityType) {
+        final int thisActivityType = getActivityType();
+        final int thisWindowingMode = getWindowingMode();
+        final boolean sameActivityType = thisActivityType == activityType;
+        final boolean sameWindowingMode = thisWindowingMode == windowingMode;
+
+        if (sameActivityType && sameWindowingMode) {
+            return true;
+        }
+
+        if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
+                || !isActivityTypeStandardOrUndefined()) {
+            // Only activity type need to match for non-standard activity types that are defined.
+            return sameActivityType;
+        }
+
+        // Otherwise we are compatible if the windowing mode is the same.
+        return sameWindowingMode;
     }
 
     public void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
@@ -252,6 +309,24 @@
         }
     }
 
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition is at
+     * {@link com.android.server.wm.proto.ConfigurationContainerProto}.
+     *
+     * @param protoOutputStream Stream to write the ConfigurationContainer object to.
+     * @param fieldId           Field Id of the ConfigurationContainer as defined in the parent
+     *                          message.
+     * @hide
+     */
+    @CallSuper
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        mOverrideConfiguration.writeToProto(protoOutputStream, OVERRIDE_CONFIGURATION);
+        mFullConfiguration.writeToProto(protoOutputStream, FULL_CONFIGURATION);
+        mMergedOverrideConfiguration.writeToProto(protoOutputStream, MERGED_OVERRIDE_CONFIGURATION);
+        protoOutputStream.end(token);
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 6cf608a..0e68a8f 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -18,9 +18,11 @@
 
 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.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 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.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -112,10 +114,11 @@
 import static com.android.server.wm.proto.DisplayProto.ROTATION;
 import static com.android.server.wm.proto.DisplayProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.proto.DisplayProto.STACKS;
+import static com.android.server.wm.proto.DisplayProto.WINDOW_CONTAINER;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.app.ActivityManager.StackId;
-import android.app.WindowConfiguration;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -389,10 +392,6 @@
                     != mTmpWindowAnimator.mAnimTransactionSequence) {
                 appAnimator.thumbnailTransactionSeq =
                         mTmpWindowAnimator.mAnimTransactionSequence;
-                appAnimator.thumbnailLayer = 0;
-            }
-            if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
-                appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
             }
         }
     };
@@ -966,10 +965,10 @@
         final int lastOrientation = mLastOrientation;
         final boolean oldAltOrientation = mAltOrientation;
         int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
-        final boolean rotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
+        boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
                 rotation);
 
-        if (rotateSeamlessly) {
+        if (mayRotateSeamlessly) {
             final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
             if (seamlessRotated != null) {
                 // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
@@ -978,7 +977,20 @@
                 // window-removal.
                 return false;
             }
+
+            // In the presence of the PINNED stack or System Alert
+            // windows we unforuntately can not seamlessly rotate.
+            if (getStackById(PINNED_STACK_ID) != null) {
+                mayRotateSeamlessly = false;
+            }
+            for (int i = 0; i < mService.mSessions.size(); i++) {
+                if (mService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
+                    mayRotateSeamlessly = false;
+                    break;
+                }
+            }
         }
+        final boolean rotateSeamlessly = mayRotateSeamlessly;
 
         // TODO: Implement forced rotation changes.
         //       Set mAltOrientation to indicate that the application is receiving
@@ -1455,16 +1467,38 @@
         return null;
     }
 
+    /**
+     * Returns the topmost stack on the display that is compatible with the input windowing mode.
+     * Null is no compatible stack on the display.
+     */
+    TaskStack getStack(int windowingMode) {
+        return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    /**
+     * 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) {
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack.isCompatible(windowingMode, activityType)) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
     @VisibleForTesting
     int getStackCount() {
         return mTaskStackContainers.size();
     }
 
     @VisibleForTesting
-    int getStackPosById(int stackId) {
+    int getStackPosition(int windowingMode, int activityType) {
         for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
             final TaskStack stack = mTaskStackContainers.get(i);
-            if (stack.mStackId == stackId) {
+            if (stack.isCompatible(windowingMode, activityType)) {
                 return i;
             }
         }
@@ -1499,7 +1533,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 (getStackById(PINNED_STACK_ID) == null) {
+        if (getStack(WINDOWING_MODE_PINNED) == null) {
             mPinnedStackControllerLocked.onDisplayInfoChanged();
         }
     }
@@ -1720,21 +1754,22 @@
         out.set(mContentRect);
     }
 
-    TaskStack addStackToDisplay(int stackId, boolean onTop) {
+    TaskStack addStackToDisplay(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 and move stack to
-            // appropriate z-order on display as needed.
+            // 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);
+            stack = new TaskStack(mService, stackId, controller);
             mTaskStackContainers.addStackToDisplay(stack, onTop);
         }
 
@@ -2023,8 +2058,8 @@
             for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
                 final TaskStack stack = mTaskStackContainers.get(i);
                 final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
-                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom) &&
-                        StackId.isStackAffectedByDragResizing(stack.mStackId)) {
+                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
+                        && stack.inSplitScreenWindowingMode()) {
                     stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
                 } else {
                     stack.resetAdjustedForIme(false);
@@ -2119,8 +2154,11 @@
         }
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         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);
@@ -2232,7 +2270,7 @@
      * @return The docked stack, but only if it is visible, and {@code null} otherwise.
      */
     TaskStack getDockedStackLocked() {
-        final TaskStack stack = getStackById(DOCKED_STACK_ID);
+        final TaskStack stack = getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         return (stack != null && stack.isVisible()) ? stack : null;
     }
 
@@ -2241,7 +2279,7 @@
      * visible.
      */
     TaskStack getDockedStackIgnoringVisibility() {
-        return getStackById(DOCKED_STACK_ID);
+        return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
     }
 
     /** Find the visible, touch-deliverable window under the given point */
@@ -3350,7 +3388,7 @@
          * @see WindowManagerService#addStackToDisplay(int, int, boolean)
          */
         void addStackToDisplay(TaskStack stack, boolean onTop) {
-            if (stack.mStackId == HOME_STACK_ID) {
+            if (stack.isActivityTypeHome()) {
                 if (mHomeStack != null) {
                     throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
                 }
@@ -3415,11 +3453,11 @@
                     : requestedPosition >= topChildPosition;
             int targetPosition = requestedPosition;
 
-            if (toTop && stack.mStackId != PINNED_STACK_ID
-                    && getStackById(PINNED_STACK_ID) != null) {
+            if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED
+                    && getStack(WINDOWING_MODE_PINNED) != null) {
                 // The pinned stack is always the top most stack (always-on-top) when it is present.
                 TaskStack topStack = mChildren.get(topChildPosition);
-                if (topStack.mStackId != PINNED_STACK_ID) {
+                if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) {
                     throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
                 }
 
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 030b986..6f441b9 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -17,8 +17,10 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.StackId.DOCKED_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.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;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Surface.ROTATION_270;
@@ -330,7 +332,7 @@
         mLastVisibility = visible;
         notifyDockedDividerVisibilityChanged(visible);
         if (!visible) {
-            setResizeDimLayer(false, INVALID_STACK_ID, 0f);
+            setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
         }
     }
 
@@ -456,7 +458,8 @@
             boolean isHomeStackResizable) {
         long animDuration = 0;
         if (animate) {
-            final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+            final TaskStack stack =
+                    mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
             final long transitionDuration = isAnimationMaximizing()
                     ? mService.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
@@ -517,9 +520,18 @@
 
     }
 
-    void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    /**
+     * Shows a dim layer with {@param alpha} if {@param visible} is true and
+     * {@param targetWindowingMode} isn't
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED} and there is a stack on the
+     * display in that windowing mode.
+     */
+    void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         mService.openSurfaceTransaction();
-        final TaskStack stack = mDisplayContent.getStackById(targetStackId);
+        // TODO: Maybe only allow split-screen windowing modes?
+        final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
+                ? mDisplayContent.getStack(targetWindowingMode)
+                : null;
         final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
@@ -605,8 +617,8 @@
         if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
             return;
         }
-        final TaskStack fullscreenStack =
-                mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
+        final TaskStack fullscreenStack = mDisplayContent.getStack(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
         final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible();
         setMinimizedDockedStack(homeVisible && !homeBehind, animate);
@@ -801,7 +813,8 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+        final TaskStack stack =
+                mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/com/android/server/wm/DragResizeMode.java b/com/android/server/wm/DragResizeMode.java
index 8ab0406..c0bf1e8 100644
--- a/com/android/server/wm/DragResizeMode.java
+++ b/com/android/server/wm/DragResizeMode.java
@@ -16,11 +16,7 @@
 
 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.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
 /**
  * Describes the mode in which a window is drag resizing.
@@ -39,15 +35,12 @@
      */
     static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
 
-    static boolean isModeAllowedForStack(int stackId, int mode) {
+    static boolean isModeAllowedForStack(TaskStack stack, int mode) {
         switch (mode) {
             case DRAG_RESIZE_MODE_FREEFORM:
-                return stackId == FREEFORM_WORKSPACE_STACK_ID;
+                return stack.getWindowingMode() == WINDOWING_MODE_FREEFORM;
             case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
-                return stackId == DOCKED_STACK_ID
-                        || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                        || stackId == HOME_STACK_ID
-                        || stackId == RECENTS_STACK_ID;
+                return stack.inSplitScreenWindowingMode();
             default:
                 return false;
         }
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
index 1e7140a..ef31598 100644
--- a/com/android/server/wm/PinnedStackController.java
+++ b/com/android/server/wm/PinnedStackController.java
@@ -16,7 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -404,27 +405,29 @@
      */
     private void notifyMovementBoundsChanged(boolean fromImeAdjustement) {
         synchronized (mService.mWindowMap) {
-            if (mPinnedStackListener != null) {
-                try {
-                    final Rect insetBounds = new Rect();
-                    getInsetBounds(insetBounds);
-                    final Rect normalBounds = getDefaultBounds();
-                    if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
-                        transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                                false /* useCurrentMinEdgeSize */);
-                    }
-                    final Rect animatingBounds = mTmpAnimatingBoundsRect;
-                    final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
-                    if (pinnedStack != null) {
-                        pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
-                    } else {
-                        animatingBounds.set(normalBounds);
-                    }
-                    mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
-                            animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
-                } catch (RemoteException e) {
-                    Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+            if (mPinnedStackListener == null) {
+                return;
+            }
+            try {
+                final Rect insetBounds = new Rect();
+                getInsetBounds(insetBounds);
+                final Rect normalBounds = getDefaultBounds();
+                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+                    transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+                            false /* useCurrentMinEdgeSize */);
                 }
+                final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                final TaskStack pinnedStack =
+                        mDisplayContent.getStack(WINDOWING_MODE_PINNED);
+                if (pinnedStack != null) {
+                    pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
+                } else {
+                    animatingBounds.set(normalBounds);
+                }
+                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
+                        animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering actions changed event.", e);
             }
         }
     }
@@ -491,7 +494,7 @@
         pw.println(prefix + "PinnedStackController");
         pw.print(prefix + "  defaultBounds="); getDefaultBounds().printShortString(pw);
         pw.println();
-        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
         pw.print(prefix + "  movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
         pw.println();
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
@@ -512,7 +515,7 @@
     void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         getDefaultBounds().writeToProto(proto, DEFAULT_BOUNDS);
-        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
         getMovementBounds(mTmpRect).writeToProto(proto, MOVEMENT_BOUNDS);
         proto.end(token);
     }
diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java
index 590ac6e..41f076d 100644
--- a/com/android/server/wm/PinnedStackWindowController.java
+++ b/com/android/server/wm/PinnedStackWindowController.java
@@ -16,19 +16,16 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
 import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
 
 import android.app.RemoteAction;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 
-import com.android.server.UiThread;
-
 import java.util.List;
 
 /**
@@ -40,8 +37,8 @@
     private Rect mTmpToBounds = new Rect();
 
     public PinnedStackWindowController(int stackId, PinnedStackWindowListener listener,
-            int displayId, boolean onTop, Rect outBounds) {
-        super(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+            int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+        super(stackId, listener, displayId, onTop, outBounds, service);
     }
 
     /**
@@ -101,7 +98,8 @@
                 }
                 schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
 
-                mService.getStackBounds(FULLSCREEN_WORKSPACE_STACK_ID, mTmpToBounds);
+                mService.getStackBounds(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mTmpToBounds);
                 if (!mTmpToBounds.isEmpty()) {
                     // If there is a fullscreen bounds, use that
                     toBounds = new Rect(mTmpToBounds);
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index 8a74976..7832f5d 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.CallSuper;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.power.V1_0.PowerHint;
@@ -89,8 +90,9 @@
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
-import static com.android.server.wm.proto.WindowManagerServiceProto.DISPLAYS;
-import static com.android.server.wm.proto.WindowManagerServiceProto.WINDOWS;
+import static com.android.server.wm.proto.RootWindowContainerProto.DISPLAYS;
+import static com.android.server.wm.proto.RootWindowContainerProto.WINDOWS;
+import static com.android.server.wm.proto.RootWindowContainerProto.WINDOW_CONTAINER;
 
 /** Root {@link WindowContainer} for the device. */
 class RootWindowContainer extends WindowContainer<DisplayContent> {
@@ -420,6 +422,17 @@
         return null;
     }
 
+    TaskStack getStack(int windowingMode, int activityType) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final DisplayContent dc = mChildren.get(i);
+            final TaskStack stack = dc.getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
     void setSecureSurfaceState(int userId, boolean disabled) {
         forAllWindows((w) -> {
             if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
@@ -1077,7 +1090,11 @@
         }
     }
 
-    void writeToProto(ProtoOutputStream proto) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         if (mService.mDisplayReady) {
             final int count = mChildren.size();
             for (int i = 0; i < count; ++i) {
@@ -1088,6 +1105,7 @@
         forAllWindows((w) -> {
             w.writeIdentifierToProto(proto, WINDOWS);
         }, true);
+        proto.end(token);
     }
 
     @Override
diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java
index 1781247..4dd147e 100644
--- a/com/android/server/wm/Session.java
+++ b/com/android/server/wm/Session.java
@@ -719,4 +719,8 @@
     public String toString() {
         return mStringName;
     }
+
+    boolean hasAlertWindowSurfaces() {
+        return !mAlertWindowSurfaces.isEmpty();
+    }
 }
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index a50ed71..c0a4cb7 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -18,8 +18,6 @@
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 
-import android.app.ActivityManager.StackId;
-import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -76,8 +74,7 @@
                         + " to unknown displayId=" + displayId);
             }
 
-            final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
-            stack.setController(this);
+            dc.addStackToDisplay(stackId, onTop, this);
             getRawBounds(outBounds);
         }
     }
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 55b6c91..7e8d130 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -23,6 +23,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.res.Configuration.EMPTY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 
 import static com.android.server.EventLogTags.WM_TASK_REMOVED;
@@ -34,8 +35,9 @@
 import static com.android.server.wm.proto.TaskProto.FILLS_PARENT;
 import static com.android.server.wm.proto.TaskProto.ID;
 import static com.android.server.wm.proto.TaskProto.TEMP_INSET_BOUNDS;
+import static com.android.server.wm.proto.TaskProto.WINDOW_CONTAINER;
 
-import android.app.ActivityManager.StackId;
+import android.annotation.CallSuper;
 import android.app.ActivityManager.TaskDescription;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -278,15 +280,9 @@
     // WindowConfiguration long term.
     private int setBounds(Rect bounds, Configuration overrideConfig) {
         if (overrideConfig == null) {
-            overrideConfig = Configuration.EMPTY;
+            overrideConfig = EMPTY;
         }
-        if (bounds == null && !Configuration.EMPTY.equals(overrideConfig)) {
-            throw new IllegalArgumentException("null bounds but non empty configuration: "
-                    + overrideConfig);
-        }
-        if (bounds != null && Configuration.EMPTY.equals(overrideConfig)) {
-            throw new IllegalArgumentException("non null bounds, but empty configuration");
-        }
+
         boolean oldFullscreen = mFillsParent;
         int rotation = Surface.ROTATION_0;
         final DisplayContent displayContent = mStack.getDisplayContent();
@@ -321,7 +317,7 @@
         if (displayContent != null) {
             displayContent.mDimLayerController.updateDimLayer(this);
         }
-        onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig);
+        onOverrideConfigurationChanged(overrideConfig);
         return boundsChange;
     }
 
@@ -404,7 +400,7 @@
      *                    the adjusted bounds's top.
      */
     void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
-        if (!isResizeable() || Configuration.EMPTY.equals(getOverrideConfiguration())) {
+        if (!isResizeable() || EMPTY.equals(getOverrideConfiguration())) {
             return;
         }
 
@@ -529,7 +525,7 @@
 
     void setDragResizing(boolean dragResizing, int dragResizeMode) {
         if (mDragResizing != dragResizing) {
-            if (!DragResizeMode.isModeAllowedForStack(mStack.mStackId, dragResizeMode)) {
+            if (!DragResizeMode.isModeAllowedForStack(mStack, dragResizeMode)) {
                 throw new IllegalArgumentException("Drag resize mode not allow for stack stackId="
                         + mStack.mStackId + " dragResizeMode=" + dragResizeMode);
             }
@@ -552,7 +548,9 @@
             return;
         }
         if (mFillsParent) {
-            setBounds(null, Configuration.EMPTY);
+            // TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a
+            // problem once we move mBounds into WindowConfiguration.
+            setBounds(null, getOverrideConfiguration());
             return;
         }
         final int newRotation = displayContent.getDisplayInfo().rotation;
@@ -735,8 +733,11 @@
         return "Task=" + mTaskId;
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(ID, mTaskId);
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final AppWindowToken appWindowToken = mChildren.get(i);
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index 4632402..bff24f6 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -254,7 +254,7 @@
     @VisibleForTesting
     int getSnapshotMode(Task task) {
         final AppWindowToken topChild = task.getTopChild();
-        if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
+        if (!task.isActivityTypeStandardOrUndefined()) {
             return SNAPSHOT_MODE_NONE;
         } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
             return SNAPSHOT_MODE_APP_THEME;
@@ -297,7 +297,7 @@
 
         return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
                 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
-                false /* reduced */, 1.0f /* scale */);
+                ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
     }
 
     /**
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 4664dcb..6527883 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -19,8 +19,11 @@
 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.HOME_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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -41,8 +44,9 @@
 import static com.android.server.wm.proto.StackProto.FILLS_PARENT;
 import static com.android.server.wm.proto.StackProto.ID;
 import static com.android.server.wm.proto.StackProto.TASKS;
+import static com.android.server.wm.proto.StackProto.WINDOW_CONTAINER;
 
-import android.app.ActivityManager.StackId;
+import android.annotation.CallSuper;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -147,9 +151,10 @@
 
     Rect mPreAnimationBounds = new Rect();
 
-    TaskStack(WindowManagerService service, int stackId) {
+    TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
         mService = service;
         mStackId = stackId;
+        setController(controller);
         mDockedStackMinimizeThickness = service.mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_minimize_thickness);
         EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId);
@@ -733,7 +738,7 @@
         outTempTaskBounds.setEmpty();
 
         // When the home stack is resizable, should always have the same stack and task bounds
-        if (mStackId == HOME_STACK_ID) {
+        if (isActivityTypeHome()) {
             final Task homeTask = findHomeTask();
             if (homeTask != null && homeTask.isResizeable()) {
                 // Calculate the home stack bounds when in docked mode and the home stack is
@@ -918,7 +923,9 @@
 
     void resetAnimationBackgroundAnimator() {
         mAnimationBackgroundAnimator = null;
-        mAnimationBackgroundSurface.hide();
+        if (mAnimationBackgroundSurface != null) {
+            mAnimationBackgroundSurface.hide();
+        }
     }
 
     void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -1005,7 +1012,7 @@
             mAdjustImeAmount = 0f;
             mAdjustDividerAmount = 0f;
             updateAdjustedBounds();
-            mService.setResizeDimLayer(false, mStackId, 1.0f);
+            mService.setResizeDimLayer(false, getWindowingMode(), 1.0f);
         } else {
             mImeGoingAway |= mAdjustedForIme;
         }
@@ -1201,7 +1208,7 @@
         if (mAdjustedForIme && adjust && !isImeTarget) {
             final float alpha = Math.max(mAdjustImeAmount, mAdjustDividerAmount)
                     * IME_ADJUST_DIM_AMOUNT;
-            mService.setResizeDimLayer(true, mStackId, alpha);
+            mService.setResizeDimLayer(true, getWindowingMode(), alpha);
         }
     }
 
@@ -1219,8 +1226,11 @@
         return mMinimizeAmount != 0f;
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(ID, mStackId);
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).writeToProto(proto, TASKS);
@@ -1277,7 +1287,7 @@
 
     @Override
     public boolean dimFullscreen() {
-        return StackId.isHomeOrRecentsStack(mStackId) || fillsParent();
+        return !isActivityTypeStandard() || fillsParent();
     }
 
     @Override
@@ -1657,7 +1667,15 @@
 
     @Override
     int getOrientation() {
-        return (StackId.canSpecifyOrientation(mStackId))
-                ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+        return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+    }
+
+    private boolean canSpecifyOrientation() {
+        final int windowingMode = getWindowingMode();
+        final int activityType = getActivityType();
+        return windowingMode == WINDOWING_MODE_FULLSCREEN
+                || activityType == ACTIVITY_TYPE_HOME
+                || activityType == ACTIVITY_TYPE_RECENTS
+                || activityType == ACTIVITY_TYPE_ASSISTANT;
     }
 }
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 926719d..40923c8 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -19,12 +19,14 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.res.Configuration.EMPTY;
+import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
+import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
 import android.util.Pools;
 
+import android.util.proto.ProtoOutputStream;
 import com.android.internal.util.ToBooleanFunction;
 
 import java.util.Comparator;
@@ -685,6 +687,23 @@
         }
     }
 
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition is at
+     * {@link com.android.server.wm.proto.WindowContainerProto}.
+     *
+     * @param protoOutputStream Stream to write the WindowContainer object to.
+     * @param fieldId           Field Id of the WindowContainer as defined in the parent message.
+     * @hide
+     */
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        super.writeToProto(protoOutputStream, CONFIGURATION_CONTAINER);
+        protoOutputStream.write(ORIENTATION, mOrientation);
+        protoOutputStream.end(token);
+    }
+
     String getName() {
         return toString();
     }
diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java
index 857b13d..7caf2fe 100644
--- a/com/android/server/wm/WindowLayersController.java
+++ b/com/android/server/wm/WindowLayersController.java
@@ -21,11 +21,8 @@
 import java.util.ArrayDeque;
 import java.util.function.Consumer;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-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.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -187,12 +184,13 @@
             }
         }
 
-        final int stackId = w.getAppToken() != null ? w.getStackId() : INVALID_STACK_ID;
-        if (stackId == PINNED_STACK_ID) {
+        final int windowingMode = w.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_PINNED) {
             mPinnedWindows.add(w);
-        } else if (stackId == DOCKED_STACK_ID) {
+        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             mDockedWindows.add(w);
-        } else if (stackId == ASSISTANT_STACK_ID) {
+        }
+        if (w.isActivityTypeAssistant()) {
             mAssistantWindows.add(w);
         }
     }
@@ -268,20 +266,8 @@
         w.mLayer = layer;
         w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
                 + w.getSpecialWindowAnimLayerAdjustment();
-        if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0) {
-            if (w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
-                w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
-            }
-            // TODO(b/62029108): the entire contents of the if statement should call the refactored
-            // function to set the thumbnail layer for w.AppToken
-            int highestLayer = w.mAppToken.getHighestAnimLayer();
-            if (highestLayer > 0) {
-                if (w.mAppToken.mAppAnimator.thumbnail != null
-                        && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer != highestLayer) {
-                    w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = highestLayer;
-                    w.mAppToken.mAppAnimator.thumbnail.setLayer(highestLayer + 1);
-                }
-            }
+        if (w.mAppToken != null) {
+            w.mAppToken.mAppAnimator.updateThumbnailLayer();
         }
     }
 }
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index 32ee51c..1fb2188 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -109,6 +109,7 @@
 import static com.android.server.wm.proto.WindowManagerServiceProto.INPUT_METHOD_WINDOW;
 import static com.android.server.wm.proto.WindowManagerServiceProto.LAST_ORIENTATION;
 import static com.android.server.wm.proto.WindowManagerServiceProto.POLICY;
+import static com.android.server.wm.proto.WindowManagerServiceProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.wm.proto.WindowManagerServiceProto.ROTATION;
 
 import android.Manifest;
@@ -246,6 +247,7 @@
 import com.android.server.input.InputManagerService;
 import com.android.server.power.BatterySaverPolicy.ServiceType;
 import com.android.server.power.ShutdownThread;
+import com.android.server.utils.PriorityDump;
 
 import java.io.BufferedWriter;
 import java.io.DataInputStream;
@@ -390,6 +392,18 @@
     };
     final WindowSurfacePlacer mWindowPlacerLocked;
 
+    private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+        @Override
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, new String[] {"-a"});
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, args);
+        }
+    };
+
     /**
      * Current user when multi-user is enabled. Don't show windows of
      * non-current user. Also see mCurrentProfileIds.
@@ -2029,10 +2043,14 @@
                 Slog.i(TAG_WM, "Relayout " + win + ": oldVis=" + oldVisibility
                         + " newVis=" + viewVisibility, stack);
             }
-            if (viewVisibility == View.VISIBLE &&
-                    (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
-                            || !win.mAppToken.isClientHidden())) {
 
+            // We should only relayout if the view is visible, it is a starting window, or the
+            // associated appToken is not hidden.
+            final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
+                (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
+                    || !win.mAppToken.isClientHidden());
+
+            if (shouldRelayout) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
 
                 // We are about to create a surface, but we didn't run a layout yet. So better run
@@ -2179,8 +2197,18 @@
             // and needs process it before handling the corresponding window frame. the variable
             // {@code mergedConfiguration} is an out parameter that will be passed back to the
             // client over IPC and checked there.
-            win.getMergedConfiguration(mergedConfiguration);
-            win.setReportedConfiguration(mergedConfiguration);
+            // Note: in the cases where the window is tied to an activity, we should not send a
+            // configuration update when the window has requested to be hidden. Doing so can lead
+            // to the client erroneously accepting a configuration that would have otherwise caused
+            // an activity restart. We instead hand back the last reported
+            // {@link MergedConfiguration}.
+            if (shouldRelayout) {
+                win.getMergedConfiguration(mergedConfiguration);
+            } else {
+                win.getLastReportedMergedConfiguration(mergedConfiguration);
+            }
+
+            win.setLastReportedMergedConfiguration(mergedConfiguration);
 
             outFrame.set(win.mCompatFrame);
             outOverscanInsets.set(win.mOverscanInsets);
@@ -2881,9 +2909,9 @@
     }
 
     @Override
-    public void getStackBounds(int stackId, Rect bounds) {
+    public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
         synchronized (mWindowMap) {
-            final TaskStack stack = mRoot.getStackById(stackId);
+            final TaskStack stack = mRoot.getStack(windowingMode, activityType);
             if (stack != null) {
                 stack.getBounds(bounds);
                 return;
@@ -6505,7 +6533,7 @@
 
     private void writeToProtoLocked(ProtoOutputStream proto) {
         mPolicy.writeToProto(proto, POLICY);
-        mRoot.writeToProto(proto);
+        mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER);
         if (mCurrentFocus != null) {
             mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
         }
@@ -6793,8 +6821,11 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        PriorityDump.dump(mPriorityDumper, fd, pw, args);
+    }
 
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
         boolean dumpAll = false;
         boolean useProto = false;
 
@@ -7124,10 +7155,10 @@
     }
 
     @Override
-    public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         synchronized (mWindowMap) {
             getDefaultDisplayContentLocked().getDockedDividerController().setResizeDimLayer(
-                    visible, targetStackId, alpha);
+                    visible, targetWindowingMode, alpha);
         }
     }
 
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 1b05566..4ff0f39 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -116,7 +116,9 @@
 import static com.android.server.wm.proto.WindowStateProto.PARENT_FRAME;
 import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
 import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.WINDOW_CONTAINER;
 
+import android.annotation.CallSuper;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -255,7 +257,7 @@
      * We'll send configuration to client only if it is different from the last applied one and
      * client won't perform unnecessary updates.
      */
-    private final Configuration mLastReportedConfiguration = new Configuration();
+    private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration();
 
     /**
      * Actual position of the surface shown on-screen (may be modified by animation). These are
@@ -1242,7 +1244,7 @@
         //                   this is not necessarily what the client has processed yet. Find a
         //                   better indicator consistent with the client.
         return (mOrientationChanging || (isVisible()
-                && getConfiguration().orientation != mLastReportedConfiguration.orientation))
+                && getConfiguration().orientation != getLastReportedConfiguration().orientation))
                 && !mSeamlesslyRotated
                 && !mOrientationChangeTimedOut;
     }
@@ -1756,7 +1758,7 @@
 
     /** Returns true if last applied config was not yet requested by client. */
     boolean isConfigChanged() {
-        return !mLastReportedConfiguration.equals(getConfiguration());
+        return !getLastReportedConfiguration().equals(getConfiguration());
     }
 
     void onWindowReplacementTimeout() {
@@ -2310,8 +2312,16 @@
         outConfiguration.setConfiguration(globalConfig, overrideConfig);
     }
 
-    void setReportedConfiguration(MergedConfiguration config) {
-        mLastReportedConfiguration.setTo(config.getMergedConfiguration());
+    void setLastReportedMergedConfiguration(MergedConfiguration config) {
+        mLastReportedConfiguration.setTo(config);
+    }
+
+    void getLastReportedMergedConfiguration(MergedConfiguration config) {
+        config.setTo(mLastReportedConfiguration);
+    }
+
+    private Configuration getLastReportedConfiguration() {
+        return mLastReportedConfiguration.getMergedConfiguration();
     }
 
     void adjustStartingWindowFlags() {
@@ -2851,7 +2861,7 @@
                     new MergedConfiguration(mService.mRoot.getConfiguration(),
                     getMergedOverrideConfiguration());
 
-            setReportedConfiguration(mergedConfiguration);
+            setLastReportedMergedConfiguration(mergedConfiguration);
 
             if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING)
                 Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
@@ -3078,7 +3088,7 @@
         if (task == null) {
             return false;
         }
-        if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+        if (!inSplitScreenWindowingMode()) {
             return false;
         }
         if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3124,8 +3134,11 @@
                 || (isChildWindow() && getParentWindow().isDockedResizing());
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(DISPLAY_ID, getDisplayId());
         proto.write(STACK_ID, getStackId());
@@ -3168,7 +3181,7 @@
                 pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
                 pw.print(" package="); pw.print(mAttrs.packageName);
                 pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
-        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
+        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs.toString(prefix));
         pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
                 pw.print(" h="); pw.print(mRequestedHeight);
                 pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
@@ -3249,7 +3262,7 @@
             }
             pw.print(prefix); pw.print("mFullConfiguration="); pw.println(getConfiguration());
             pw.print(prefix); pw.print("mLastReportedConfiguration=");
-                    pw.println(mLastReportedConfiguration);
+                    pw.println(getLastReportedConfiguration());
         }
         pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
                 pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
@@ -3309,7 +3322,7 @@
             pw.print(prefix); pw.print("mOrientationChanging=");
                     pw.print(mOrientationChanging);
                     pw.print(" configOrientationChanging=");
-                    pw.print(mLastReportedConfiguration.orientation
+                    pw.print(getLastReportedConfiguration().orientation
                             != getConfiguration().orientation);
                     pw.print(" mAppFreezing="); pw.print(mAppFreezing);
                     pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index 88625d3..af1fa2f 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -342,10 +342,7 @@
         mTmpLayerAndToken.token = null;
         handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
         final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
-        final int topClosingLayer = mTmpLayerAndToken.layer;
-
-        final AppWindowToken topOpeningApp = handleOpeningApps(transit,
-                animLp, voiceInteraction, topClosingLayer);
+        final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction);
 
         mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
 
@@ -387,8 +384,9 @@
     }
 
     private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp,
-            boolean voiceInteraction, int topClosingLayer) {
+            boolean voiceInteraction) {
         AppWindowToken topOpeningApp = null;
+        int topOpeningLayer = Integer.MIN_VALUE;
         final int appsCount = mService.mOpeningApps.size();
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
@@ -422,7 +420,6 @@
             }
             mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
-            int topOpeningLayer = 0;
             if (animLp != null) {
                 final int layer = wtoken.getHighestAnimLayer();
                 if (topOpeningApp == null || layer > topOpeningLayer) {
@@ -431,7 +428,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
-                createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+                createThumbnailAppAnimator(transit, wtoken);
             }
         }
         return topOpeningApp;
@@ -473,7 +470,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
-                createThumbnailAppAnimator(transit, wtoken, 0, layerAndToken.layer);
+                createThumbnailAppAnimator(transit, wtoken);
             }
         }
     }
@@ -666,8 +663,7 @@
         }
     }
 
-    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken,
-            int openingLayer, int closingLayer) {
+    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
         AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
         if (openingAppAnimator == null || openingAppAnimator.animation == null) {
             return;
@@ -724,7 +720,6 @@
                 anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
                         insets, thumbnailHeader, taskId, displayConfig.uiMode,
                         displayConfig.orientation);
-                openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
                 openingAppAnimator.deferThumbnailDestruction =
                         !mService.mAppTransition.isNextThumbnailTransitionScaleUp();
             } else {
@@ -734,8 +729,8 @@
             anim.restrictDuration(MAX_ANIMATION_DURATION);
             anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
 
+            openingAppAnimator.updateThumbnailLayer();
             openingAppAnimator.thumbnail = surfaceControl;
-            openingAppAnimator.thumbnailLayer = openingLayer;
             openingAppAnimator.thumbnailAnimation = anim;
             mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
         } catch (Surface.OutOfResourcesException e) {
diff --git a/com/android/server/wm/WindowToken.java b/com/android/server/wm/WindowToken.java
index 422615b..943448e 100644
--- a/com/android/server/wm/WindowToken.java
+++ b/com/android/server/wm/WindowToken.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.CallSuper;
 import android.util.proto.ProtoOutputStream;
 import java.util.Comparator;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -28,6 +29,7 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.proto.WindowTokenProto.HASH_CODE;
 import static com.android.server.wm.proto.WindowTokenProto.WINDOWS;
+import static com.android.server.wm.proto.WindowTokenProto.WINDOW_CONTAINER;
 
 import android.os.Debug;
 import android.os.IBinder;
@@ -263,8 +265,11 @@
         super.onDisplayChanged(dc);
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(HASH_CODE, System.identityHashCode(this));
         for (int i = 0; i < mChildren.size(); i++) {
             final WindowState w = mChildren.get(i);
diff --git a/com/android/settingslib/applications/ApplicationsState.java b/com/android/settingslib/applications/ApplicationsState.java
index 40c2b1f..fa2499f 100644
--- a/com/android/settingslib/applications/ApplicationsState.java
+++ b/com/android/settingslib/applications/ApplicationsState.java
@@ -52,6 +52,11 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
 
 import java.io.File;
 import java.io.IOException;
@@ -180,7 +185,11 @@
     }
 
     public Session newSession(Callbacks callbacks) {
-        Session s = new Session(callbacks);
+        return newSession(callbacks, null);
+    }
+
+    public Session newSession(Callbacks callbacks, Lifecycle lifecycle) {
+        Session s = new Session(callbacks, lifecycle);
         synchronized (mEntriesMap) {
             mSessions.add(s);
         }
@@ -586,7 +595,7 @@
                 .replaceAll("").toLowerCase();
     }
 
-    public class Session {
+    public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy {
         final Callbacks mCallbacks;
         boolean mResumed;
 
@@ -600,11 +609,19 @@
         ArrayList<AppEntry> mLastAppList;
         boolean mRebuildForeground;
 
-        Session(Callbacks callbacks) {
+        private final boolean mHasLifecycle;
+
+        Session(Callbacks callbacks, Lifecycle lifecycle) {
             mCallbacks = callbacks;
+            if (lifecycle != null) {
+                lifecycle.addObserver(this);
+                mHasLifecycle = true;
+            } else {
+                mHasLifecycle = false;
+            }
         }
 
-        public void resume() {
+        public void onResume() {
             if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
             synchronized (mEntriesMap) {
                 if (!mResumed) {
@@ -616,7 +633,7 @@
             if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
         }
 
-        public void pause() {
+        public void onPause() {
             if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
             synchronized (mEntriesMap) {
                 if (mResumed) {
@@ -735,8 +752,11 @@
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
 
-        public void release() {
-            pause();
+        public void onDestroy() {
+            if (!mHasLifecycle) {
+                // TODO: Legacy, remove this later once all usages are switched to Lifecycle
+                onPause();
+            }
             synchronized (mEntriesMap) {
                 mSessions.remove(this);
             }
diff --git a/com/android/settingslib/applications/StorageStatsSource.java b/com/android/settingslib/applications/StorageStatsSource.java
index 8fc9fa6..9fbadee 100644
--- a/com/android/settingslib/applications/StorageStatsSource.java
+++ b/com/android/settingslib/applications/StorageStatsSource.java
@@ -131,7 +131,7 @@
         }
 
         public long getTotalBytes() {
-            return mStats.getCacheBytes() + mStats.getCodeBytes() + mStats.getDataBytes();
+            return mStats.getAppBytes() + mStats.getDataBytes();
         }
     }
 }
\ No newline at end of file
diff --git a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 47cbb77..6aae226 100644
--- a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -28,17 +28,20 @@
 import android.support.v7.preference.TwoStatePreference;
 import android.text.TextUtils;
 
-import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.ConfirmationDialogController;
 
-public abstract class AbstractEnableAdbPreferenceController extends AbstractPreferenceController
-        implements ConfirmationDialogController {
+public abstract class AbstractEnableAdbPreferenceController extends
+        DeveloperOptionsPreferenceController implements ConfirmationDialogController {
     private static final String KEY_ENABLE_ADB = "enable_adb";
     public static final String ACTION_ENABLE_ADB_STATE_CHANGED =
             "com.android.settingslib.development.AbstractEnableAdbController."
                     + "ENABLE_ADB_STATE_CHANGED";
 
-    private SwitchPreference mPreference;
+    public static final int ADB_SETTING_ON = 1;
+    public static final int ADB_SETTING_OFF = 0;
+
+
+    protected SwitchPreference mPreference;
 
     public AbstractEnableAdbPreferenceController(Context context) {
         super(context);
@@ -64,12 +67,13 @@
 
     private boolean isAdbEnabled() {
         final ContentResolver cr = mContext.getContentResolver();
-        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) != 0;
+        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, ADB_SETTING_OFF)
+                != ADB_SETTING_OFF;
     }
 
     @Override
     public void updateState(Preference preference) {
-        ((TwoStatePreference)preference).setChecked(isAdbEnabled());
+        ((TwoStatePreference) preference).setChecked(isAdbEnabled());
     }
 
     public void enablePreference(boolean enabled) {
@@ -105,7 +109,7 @@
 
     protected void writeAdbSetting(boolean enabled) {
         Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
+                Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
         notifyStateChanged();
     }
 
diff --git a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
index c167723..7998b2e 100644
--- a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
@@ -26,10 +26,9 @@
 import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
 
-public abstract class AbstractLogdSizePreferenceController extends AbstractPreferenceController
-        implements Preference.OnPreferenceChangeListener {
+public abstract class AbstractLogdSizePreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener {
     public static final String ACTION_LOGD_SIZE_UPDATED = "com.android.settingslib.development."
             + "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED";
     public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE";
@@ -57,11 +56,6 @@
     }
 
     @Override
-    public boolean isAvailable() {
-        return true;
-    }
-
-    @Override
     public String getPreferenceKey() {
         return SELECT_LOGD_SIZE_KEY;
     }
diff --git a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
index 502fb17..67553ad 100644
--- a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
@@ -30,16 +30,15 @@
 import android.text.TextUtils;
 
 import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.ConfirmationDialogController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnCreate;
 import com.android.settingslib.core.lifecycle.events.OnDestroy;
 
-public abstract class AbstractLogpersistPreferenceController extends AbstractPreferenceController
-        implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnCreate, OnDestroy,
-        ConfirmationDialogController {
+public abstract class AbstractLogpersistPreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
+        LifecycleObserver, OnCreate, OnDestroy, ConfirmationDialogController {
 
     private static final String SELECT_LOGPERSIST_KEY = "select_logpersist";
     private static final String SELECT_LOGPERSIST_PROPERTY = "persist.logd.logpersistd";
diff --git a/com/android/settingslib/development/DeveloperOptionsPreferenceController.java b/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
new file mode 100644
index 0000000..f68c04f
--- /dev/null
+++ b/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * 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.development;
+
+import android.content.Context;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * This controller is used handle changes for the master switch in the developer options page.
+ *
+ * All Preference Controllers that are a part of the developer options page should inherit this
+ * class.
+ */
+public abstract class DeveloperOptionsPreferenceController extends
+        AbstractPreferenceController {
+
+    public DeveloperOptionsPreferenceController(Context context) {
+        super(context);
+    }
+
+    /**
+     * Child classes should override this method to create custom logic for hiding preferences.
+     *
+     * @return true if the preference is to be displayed.
+     */
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    /**
+     * Called when developer options is enabled
+     */
+    public void onDeveloperOptionsEnabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchEnabled();
+        }
+    }
+
+    /**
+     * Called when developer options is disabled
+     */
+    public void onDeveloperOptionsDisabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchDisabled();
+        }
+    }
+
+    /**
+     * Called when developer options is enabled and the preference is available
+     */
+    protected void onDeveloperOptionsSwitchEnabled() {
+    }
+
+    /**
+     * Called when developer options is disabled and the preference is available
+     */
+    protected void onDeveloperOptionsSwitchDisabled() {
+    }
+
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
new file mode 100644
index 0000000..ff7536a
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.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 com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
+ */
+public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
+    private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+    private final String mSerialNumber;
+
+    public AbstractSerialNumberPreferenceController(Context context) {
+        this(context, Build.getSerial());
+    }
+
+    @VisibleForTesting
+    AbstractSerialNumberPreferenceController(Context context, String serialNumber) {
+        super(context);
+        mSerialNumber = serialNumber;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !TextUtils.isEmpty(mSerialNumber);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final Preference pref = screen.findPreference(KEY_SERIAL_NUMBER);
+        if (pref != null) {
+            pref.setSummary(mSerialNumber);
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SERIAL_NUMBER;
+    }
+}
diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java
index 9b75c00..35ba6ae 100644
--- a/com/android/settingslib/drawer/TileUtils.java
+++ b/com/android/settingslib/drawer/TileUtils.java
@@ -26,7 +26,6 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -320,6 +319,15 @@
             Context context, UserHandle user, Intent intent,
             Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
             boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
+        getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
+                usePriority, checkCategory, forceTintExternalIcon, false /* shouldUpdateTiles */);
+    }
+
+    public static void getTilesForIntent(
+            Context context, UserHandle user, Intent intent,
+            Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
+            boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,
+            boolean shouldUpdateTiles) {
         PackageManager pm = context.getPackageManager();
         List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                 PackageManager.GET_META_DATA, user.getIdentifier());
@@ -357,9 +365,11 @@
                 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
                         pm, providerMap, forceTintExternalIcon);
                 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
-
                 addedCache.put(key, tile);
+            } else if (shouldUpdateTiles) {
+                updateSummaryAndTitle(context, providerMap, tile);
             }
+
             if (!tile.userHandle.contains(user)) {
                 tile.userHandle.add(user);
             }
@@ -380,7 +390,6 @@
             String summary = null;
             String keyHint = null;
             boolean isIconTintable = false;
-            RemoteViews remoteViews = null;
 
             // Get the activity's meta-data
             try {
@@ -428,7 +437,8 @@
                     }
                     if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
                         int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
-                        remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+                        tile.remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+                        updateSummaryAndTitle(context, providerMap, tile);
                     }
                 }
             } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
@@ -462,7 +472,6 @@
             // Suggest a key for this tile
             tile.key = keyHint;
             tile.isIconTintable = isIconTintable;
-            tile.remoteViews = remoteViews;
 
             return true;
         }
@@ -470,6 +479,26 @@
         return false;
     }
 
+    private static void updateSummaryAndTitle(
+            Context context, Map<String, IContentProvider> providerMap, Tile tile) {
+        if (tile == null || tile.metaData == null
+                || !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
+            return;
+        }
+
+        String uriString = tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI);
+        Bundle bundle = getBundleFromUri(context, uriString, providerMap);
+        String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
+        String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
+        if (overrideSummary != null) {
+            tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
+        }
+
+        if (overrideTitle != null) {
+            tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
+        }
+    }
+
     /**
      * Gets the icon package name and resource id from content provider.
      * @param context context
@@ -535,37 +564,6 @@
         }
     }
 
-    public static void updateTileUsingSummaryUri(Context context, final Tile tile) {
-        if (tile == null || tile.metaData == null ||
-                !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
-            return;
-        }
-
-        new AsyncTask<Void, Void, Bundle>() {
-            @Override
-            protected Bundle doInBackground(Void... params) {
-                return getBundleFromUri(context,
-                        tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI), new HashMap<>());
-            }
-
-            @Override
-            protected void onPostExecute(Bundle bundle) {
-                if (bundle == null) {
-                    return;
-                }
-                final String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
-                final String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
-
-                if (overrideSummary != null) {
-                    tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
-                }
-                if (overrideTitle != null) {
-                    tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
-                }
-            }
-        }.execute();
-    }
-
     private static String getString(Bundle bundle, String key) {
         return bundle == null ? null : bundle.getString(key);
     }
diff --git a/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index b7fd404..3c5ac8d 100644
--- a/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -73,7 +73,7 @@
         final Drawable deviceDrawable = context.getDrawable(resId);
 
         final BatteryMeterDrawable batteryDrawable = new BatteryMeterDrawable(context,
-                R.color.meter_background_color, batteryLevel);
+                context.getColor(R.color.meter_background_color), batteryLevel);
         final int pad = context.getResources().getDimensionPixelSize(R.dimen.bt_battery_padding);
         batteryDrawable.setPadding(pad, pad, pad, pad);
 
@@ -107,6 +107,8 @@
     @VisibleForTesting
     static class BatteryMeterDrawable extends BatteryMeterDrawableBase {
         private final float mAspectRatio;
+        @VisibleForTesting
+        int mFrameColor;
 
         public BatteryMeterDrawable(Context context, int frameColor, int batteryLevel) {
             super(context, frameColor);
@@ -118,6 +120,7 @@
             final int tintColor = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
             setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
             setBatteryLevel(batteryLevel);
+            mFrameColor = frameColor;
         }
 
         @Override
diff --git a/com/android/settingslib/suggestions/SuggestionParser.java b/com/android/settingslib/suggestions/SuggestionParser.java
index 00f32b2..56b8441 100644
--- a/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/com/android/settingslib/suggestions/SuggestionParser.java
@@ -195,7 +195,7 @@
             intent.setPackage(category.pkg);
         }
         TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
-                mAddCache, null, suggestions, true, false, false);
+                mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);
         filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
         if (!category.multiple && suggestions.size() > (countBefore + 1)) {
             // If there are too many, remove them all and only re-add the one with the highest
diff --git a/com/android/settingslib/wifi/WifiTracker.java b/com/android/settingslib/wifi/WifiTracker.java
index 664dcfc..12455d8 100644
--- a/com/android/settingslib/wifi/WifiTracker.java
+++ b/com/android/settingslib/wifi/WifiTracker.java
@@ -24,7 +24,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkKey;
 import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
@@ -36,10 +35,14 @@
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiNetworkScoreCache.CacheListener;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.provider.Settings;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -47,8 +50,12 @@
 import android.util.SparseIntArray;
 import android.widget.Toast;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,7 +71,7 @@
 /**
  * Tracks saved or available wifi networks and their state.
  */
-public class WifiTracker {
+public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
     /**
      * Default maximum age in millis of cached scored networks in
      * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
@@ -80,7 +87,7 @@
      * and used so as to assist with in-the-field WiFi connectivity debugging  */
     public static boolean sVerboseLogging;
 
-    // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
+    // TODO(b/36733768): Remove flag includeSaved
 
     // TODO: Allow control of this?
     // Combo scans can take 5-6s to complete - set to 10s.
@@ -96,9 +103,9 @@
     private final WifiListener mListener;
     private final boolean mIncludeSaved;
     private final boolean mIncludeScans;
-    private final boolean mIncludePasspoints;
-    @VisibleForTesting final MainHandler mMainHandler;
-    @VisibleForTesting final WorkHandler mWorkHandler;
+    @VisibleForTesting MainHandler mMainHandler;
+    @VisibleForTesting WorkHandler mWorkHandler;
+    private HandlerThread mWorkThread;
 
     private WifiTrackerNetworkCallback mNetworkCallback;
 
@@ -142,7 +149,7 @@
     private WifiInfo mLastInfo;
 
     private final NetworkScoreManager mNetworkScoreManager;
-    private final WifiNetworkScoreCache mScoreCache;
+    private WifiNetworkScoreCache mScoreCache;
     private boolean mNetworkScoringUiEnabled;
     private long mMaxSpeedLabelScoreCacheAge;
 
@@ -169,51 +176,43 @@
         return filter;
     }
 
+    /**
+     * Use the lifecycle constructor below whenever possible
+     */
+    @Deprecated
     public WifiTracker(Context context, WifiListener wifiListener,
             boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, null, includeSaved, includeScans);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                       boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
+        this(context, wifiListener, includeSaved, includeScans,
                 context.getSystemService(WifiManager.class),
                 context.getSystemService(ConnectivityManager.class),
                 context.getSystemService(NetworkScoreManager.class),
-                Looper.myLooper(), newIntentFilter());
+                newIntentFilter());
+    }
+
+    public WifiTracker(Context context, WifiListener wifiListener,
+            @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
+        this(context, wifiListener, includeSaved, includeScans,
+                context.getSystemService(WifiManager.class),
+                context.getSystemService(ConnectivityManager.class),
+                context.getSystemService(NetworkScoreManager.class),
+                newIntentFilter());
+        lifecycle.addObserver(this);
     }
 
     @VisibleForTesting
-    WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                boolean includeSaved, boolean includeScans, boolean includePasspoints,
-                WifiManager wifiManager, ConnectivityManager connectivityManager,
-                NetworkScoreManager networkScoreManager, Looper currentLooper,
-                IntentFilter filter) {
+    WifiTracker(Context context, WifiListener wifiListener,
+            boolean includeSaved, boolean includeScans,
+            WifiManager wifiManager, ConnectivityManager connectivityManager,
+            NetworkScoreManager networkScoreManager,
+            IntentFilter filter) {
         if (!includeSaved && !includeScans) {
             throw new IllegalArgumentException("Must include either saved or scans");
         }
         mContext = context;
-        if (currentLooper == null) {
-            // When we aren't on a looper thread, default to the main.
-            currentLooper = Looper.getMainLooper();
-        }
-        mMainHandler = new MainHandler(currentLooper);
-        mWorkHandler = new WorkHandler(
-                workerLooper != null ? workerLooper : currentLooper);
+        mMainHandler = new MainHandler(Looper.getMainLooper());
         mWifiManager = wifiManager;
         mIncludeSaved = includeSaved;
         mIncludeScans = includeScans;
-        mIncludePasspoints = includePasspoints;
         mListener = wifiListener;
         mConnectivityManager = connectivityManager;
 
@@ -229,7 +228,22 @@
 
         mNetworkScoreManager = networkScoreManager;
 
-        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
+        final HandlerThread workThread = new HandlerThread(TAG
+                + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        workThread.start();
+        setWorkThread(workThread);
+    }
+
+    /**
+     * Sanity warning: this wipes out mScoreCache, so use with extreme caution
+     * @param workThread substitute Handler thread, for testing purposes only
+     */
+    @VisibleForTesting
+    void setWorkThread(HandlerThread workThread) {
+        mWorkThread = workThread;
+        mWorkHandler = new WorkHandler(workThread.getLooper());
+        mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
             @Override
             public void networkCacheUpdated(List<ScoredNetwork> networks) {
                 synchronized (mLock) {
@@ -244,6 +258,11 @@
         });
     }
 
+    @Override
+    public void onDestroy() {
+        mWorkThread.quit();
+    }
+
     /** Synchronously update the list of access points with the latest information. */
     @MainThread
     public void forceUpdate() {
@@ -312,8 +331,9 @@
      * <p>Registers listeners and starts scanning for wifi networks. If this is not called
      * then forceUpdate() must be called to populate getAccessPoints().
      */
+    @Override
     @MainThread
-    public void startTracking() {
+    public void onStart() {
         synchronized (mLock) {
             registerScoreCache();
 
@@ -361,15 +381,16 @@
     /**
      * Stop tracking wifi networks and scores.
      *
-     * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
+     * <p>This should always be called when done with a WifiTracker (if onStart was called) to
      * ensure proper cleanup and prevent any further callbacks from occurring.
      *
      * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
      * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
      * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
      */
+    @Override
     @MainThread
-    public void stopTracking() {
+    public void onStop() {
         synchronized (mLock) {
             if (mRegistered) {
                 mContext.unregisterReceiver(mReceiver);
@@ -769,9 +790,8 @@
     }
 
     public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
-            boolean includeScans, boolean includePasspoints) {
-        WifiTracker tracker = new WifiTracker(context,
-                null, null, includeSaved, includeScans, includePasspoints);
+            boolean includeScans) {
+        WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans);
         tracker.forceUpdate();
         tracker.copyAndNotifyListeners(false /*notifyListeners*/);
         return tracker.getAccessPoints();
diff --git a/com/android/settingslib/wifi/WifiTrackerFactory.java b/com/android/settingslib/wifi/WifiTrackerFactory.java
index 79cee04..8b5863a 100644
--- a/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -16,8 +16,10 @@
 package com.android.settingslib.wifi;
 
 import android.content.Context;
-import android.os.Looper;
 import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 /**
  * Factory method used to inject WifiTracker instances.
@@ -31,12 +33,11 @@
     }
 
     public static WifiTracker create(
-            Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
+            Context context, WifiTracker.WifiListener wifiListener, @NonNull Lifecycle lifecycle,
+            boolean includeSaved, boolean includeScans) {
         if(sTestingWifiTracker != null) {
             return sTestingWifiTracker;
         }
-        return new WifiTracker(
-                context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints);
+        return new WifiTracker(context, wifiListener, lifecycle, includeSaved, includeScans);
     }
 }
diff --git a/com/android/settingslib/wrapper/PackageManagerWrapper.java b/com/android/settingslib/wrapper/PackageManagerWrapper.java
index cd62bc3..b1f3f3c 100644
--- a/com/android/settingslib/wrapper/PackageManagerWrapper.java
+++ b/com/android/settingslib/wrapper/PackageManagerWrapper.java
@@ -60,6 +60,13 @@
     }
 
     /**
+     * Calls {@code PackageManager.getInstalledPackagesAsUser}
+     */
+    public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+        return mPm.getInstalledPackagesAsUser(flags, userId);
+    }
+
+    /**
      * Calls {@code PackageManager.hasSystemFeature()}.
      *
      * @see android.content.pm.PackageManager#hasSystemFeature
@@ -132,11 +139,11 @@
 
     /**
      * Gets information about a particular package from the package manager.
+     *
      * @param packageName The name of the package we would like information about.
-     * @param i additional options flags. see javadoc for
-     * {@link PackageManager#getPackageInfo(String, int)}
+     * @param i           additional options flags. see javadoc for
+     *                    {@link PackageManager#getPackageInfo(String, int)}
      * @return The PackageInfo for the requested package
-     * @throws NameNotFoundException
      */
     public PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException {
         return mPm.getPackageInfo(packageName, i);
@@ -144,6 +151,7 @@
 
     /**
      * Retrieves the icon associated with this particular set of ApplicationInfo
+     *
      * @param info The ApplicationInfo to retrieve the icon for
      * @return The icon as a drawable.
      */
@@ -154,6 +162,7 @@
 
     /**
      * Retrieves the label associated with the particular set of ApplicationInfo
+     *
      * @param app The ApplicationInfo to retrieve the label for
      * @return the label as a CharSequence
      */
@@ -190,4 +199,48 @@
             throws PackageManager.NameNotFoundException {
         return mPm.getPackageUidAsUser(pkg, userId);
     }
+
+    /**
+     * Calls {@code PackageManager.setApplicationEnabledSetting}
+     */
+    public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
+        mPm.setApplicationEnabledSetting(packageName, newState, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationEnabledSetting}
+     */
+    public int getApplicationEnabledSetting(String packageName) {
+        return mPm.getApplicationEnabledSetting(packageName);
+    }
+
+    /**
+     * Calls {@code PackageManager.setComponentEnabledSetting}
+     */
+    public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
+        mPm.setComponentEnabledSetting(componentName, newState, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationInfo}
+     */
+    public ApplicationInfo getApplicationInfo(String packageName, int flags)
+            throws NameNotFoundException {
+        return mPm.getApplicationInfo(packageName, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationLabel}
+     */
+    public CharSequence getApplicationLabel(ApplicationInfo info) {
+        return mPm.getApplicationLabel(info);
+    }
+
+    /**
+     * Calls {@code PackageManager.queryBroadcastReceivers}
+     */
+    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+        return mPm.queryBroadcastReceivers(intent, flags);
+    }
 }
+
diff --git a/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
index 154339a..3d11e12 100644
--- a/com/android/setupwizardlib/test/util/DrawingTestActivity.java
+++ b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
@@ -16,15 +16,14 @@
 
 package com.android.setupwizardlib.test.util;
 
-import android.support.v7.app.AppCompatActivity;
+import android.app.Activity;
 
 /**
  * Activity to test view and drawable drawing behaviors. This is used to make sure that the drawing
- * behavior tested is the same as it would when inflated as part of an {@link AppCompatActivity},
- * including custom layout inflaters and theme values that the support library injects to the
- * activity.
+ * behavior tested is the same as it would when inflated as part of an activity, including any
+ * injected layout inflater factories and custom themes etc.
  *
  * @see DrawingTestHelper
  */
-public class DrawingTestActivity extends AppCompatActivity {
+public class DrawingTestActivity extends Activity {
 }
diff --git a/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
index 1e663d6..1fb3a37 100644
--- a/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
+++ b/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
@@ -19,6 +19,8 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
@@ -38,12 +40,11 @@
 /**
  * An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and
  * clicked by accessibility services.
- * <p>
- * <strong>Note: </strong> From Android O on, there is native support for ClickableSpan
- * accessibility, so this class is not needed (and indeed has no effect.)
- * </p>
  *
- * <p />Sample usage:
+ * <p><strong>Note:</strong> This class is a no-op on Android O or above since there is native
+ * support for ClickableSpan accessibility.
+ *
+ * <p>Sample usage:
  * <pre>
  * LinkAccessibilityHelper mAccessibilityHelper;
  *
@@ -68,294 +69,255 @@
 
     private static final String TAG = "LinkAccessibilityHelper";
 
-    private final TextView mView;
-    private final Rect mTempRect = new Rect();
-    private final ExploreByTouchHelper mExploreByTouchHelper;
+    private final AccessibilityDelegateCompat mDelegate;
 
     public LinkAccessibilityHelper(TextView view) {
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
-            // Pre-O, we essentially extend ExploreByTouchHelper to expose a virtual view hierarchy
-            mExploreByTouchHelper = new ExploreByTouchHelper(view) {
-                @Override
-                protected int getVirtualViewAt(float x, float y) {
-                    return LinkAccessibilityHelper.this.getVirtualViewAt(x, y);
-                }
+        this(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                // Platform support was added in O. This helper will be no-op
+                ? new AccessibilityDelegateCompat()
+                // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy
+                : new PreOLinkAccessibilityHelper(view));
+    }
 
-                @Override
-                protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
-                    LinkAccessibilityHelper.this.getVisibleVirtualViews(virtualViewIds);
-                }
-
-                @Override
-                protected void onPopulateEventForVirtualView(int virtualViewId,
-                        AccessibilityEvent event) {
-                    LinkAccessibilityHelper
-                            .this.onPopulateEventForVirtualView(virtualViewId, event);
-                }
-
-                @Override
-                protected void onPopulateNodeForVirtualView(int virtualViewId,
-                        AccessibilityNodeInfoCompat infoCompat) {
-                    LinkAccessibilityHelper
-                            .this.onPopulateNodeForVirtualView(virtualViewId, infoCompat);
-
-                }
-
-                @Override
-                protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
-                        Bundle arguments) {
-                    return LinkAccessibilityHelper.this
-                            .onPerformActionForVirtualView(virtualViewId, action, arguments);
-                }
-            };
-        } else {
-            mExploreByTouchHelper = null;
-        }
-        mView = view;
+    @VisibleForTesting
+    LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) {
+        mDelegate = delegate;
     }
 
     @Override
     public void sendAccessibilityEvent(View host, int eventType) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.sendAccessibilityEvent(host, eventType);
-        } else {
-            super.sendAccessibilityEvent(host, eventType);
-        }
+        mDelegate.sendAccessibilityEvent(host, eventType);
     }
 
     @Override
     public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.sendAccessibilityEventUnchecked(host, event);
-        } else {
-            super.sendAccessibilityEventUnchecked(host, event);
-        }
+        mDelegate.sendAccessibilityEventUnchecked(host, event);
     }
 
     @Override
     public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.dispatchPopulateAccessibilityEvent(host, event)
-                : super.dispatchPopulateAccessibilityEvent(host, event);
+        return mDelegate.dispatchPopulateAccessibilityEvent(host, event);
     }
 
     @Override
     public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.onPopulateAccessibilityEvent(host, event);
-        } else {
-            super.onPopulateAccessibilityEvent(host, event);
-        }
+        mDelegate.onPopulateAccessibilityEvent(host, event);
     }
 
     @Override
     public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.onInitializeAccessibilityEvent(host, event);
-        } else {
-            super.onInitializeAccessibilityEvent(host, event);
-        }
+        mDelegate.onInitializeAccessibilityEvent(host, event);
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.onInitializeAccessibilityNodeInfo(host, info);
-        } else {
-            super.onInitializeAccessibilityNodeInfo(host, info);
-        }
+        mDelegate.onInitializeAccessibilityNodeInfo(host, info);
     }
 
     @Override
     public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
             AccessibilityEvent event) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.onRequestSendAccessibilityEvent(host, child, event)
-                : super.onRequestSendAccessibilityEvent(host, child, event);
+        return mDelegate.onRequestSendAccessibilityEvent(host, child, event);
     }
 
     @Override
     public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.getAccessibilityNodeProvider(host)
-                : super.getAccessibilityNodeProvider(host);
+        return mDelegate.getAccessibilityNodeProvider(host);
     }
 
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.performAccessibilityAction(host, action, args)
-                : super.performAccessibilityAction(host, action, args);
+        return mDelegate.performAccessibilityAction(host, action, args);
     }
 
     /**
-     * Delegated to {@link ExploreByTouchHelper}
+     * Dispatches hover event to the virtual view hierarchy. This method should be called in
+     * {@link View#dispatchHoverEvent(MotionEvent)}.
+     *
+     * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent)
      */
     public final boolean dispatchHoverEvent(MotionEvent event) {
-        return (mExploreByTouchHelper != null) ? mExploreByTouchHelper.dispatchHoverEvent(event)
-                : false;
+        return mDelegate instanceof ExploreByTouchHelper
+                && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event);
     }
 
-    protected int getVirtualViewAt(float x, float y) {
-        final CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            final Spanned spannedText = (Spanned) text;
-            final int offset = getOffsetForPosition(mView, x, y);
-            ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
-            if (linkSpans.length == 1) {
-                ClickableSpan linkSpan = linkSpans[0];
-                return spannedText.getSpanStart(linkSpan);
+    @VisibleForTesting
+    static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper {
+
+        private final Rect mTempRect = new Rect();
+        private final TextView mView;
+
+        PreOLinkAccessibilityHelper(TextView view) {
+            super(view);
+            mView = view;
+        }
+
+        protected int getVirtualViewAt(float x, float y) {
+            final CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                final Spanned spannedText = (Spanned) text;
+                final int offset = getOffsetForPosition(mView, x, y);
+                ClickableSpan[] linkSpans =
+                        spannedText.getSpans(offset, offset, ClickableSpan.class);
+                if (linkSpans.length == 1) {
+                    ClickableSpan linkSpan = linkSpans[0];
+                    return spannedText.getSpanStart(linkSpan);
+                }
+            }
+            return ExploreByTouchHelper.INVALID_ID;
+        }
+
+        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+            final CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                final Spanned spannedText = (Spanned) text;
+                ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
+                        ClickableSpan.class);
+                for (ClickableSpan span : linkSpans) {
+                    virtualViewIds.add(spannedText.getSpanStart(span));
+                }
             }
         }
-        return ExploreByTouchHelper.INVALID_ID;
-    }
 
-    protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
-        final CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            final Spanned spannedText = (Spanned) text;
-            ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
-                    ClickableSpan.class);
-            for (ClickableSpan span : linkSpans) {
-                virtualViewIds.add(spannedText.getSpanStart(span));
-            }
-        }
-    }
-
-    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
-        final ClickableSpan span = getSpanForOffset(virtualViewId);
-        if (span != null) {
-            event.setContentDescription(getTextForSpan(span));
-        } else {
-            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
-            event.setContentDescription(mView.getText());
-        }
-    }
-
-    protected void onPopulateNodeForVirtualView(int virtualViewId,
-            AccessibilityNodeInfoCompat info) {
-        final ClickableSpan span = getSpanForOffset(virtualViewId);
-        if (span != null) {
-            info.setContentDescription(getTextForSpan(span));
-        } else {
-            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
-            info.setContentDescription(mView.getText());
-        }
-        info.setFocusable(true);
-        info.setClickable(true);
-        getBoundsForSpan(span, mTempRect);
-        if (mTempRect.isEmpty()) {
-            Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
-            mTempRect.set(0, 0, 1, 1);
-        }
-        info.setBoundsInParent(mTempRect);
-        info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
-    }
-
-    protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
-            Bundle arguments) {
-        if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
-            ClickableSpan span = getSpanForOffset(virtualViewId);
+        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+            final ClickableSpan span = getSpanForOffset(virtualViewId);
             if (span != null) {
-                span.onClick(mView);
-                return true;
+                event.setContentDescription(getTextForSpan(span));
             } else {
                 Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+                event.setContentDescription(mView.getText());
             }
         }
-        return false;
-    }
 
-    private ClickableSpan getSpanForOffset(int offset) {
-        CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            Spanned spannedText = (Spanned) text;
-            ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
-            if (spans.length == 1) {
-                return spans[0];
+        protected void onPopulateNodeForVirtualView(
+                int virtualViewId,
+                AccessibilityNodeInfoCompat info) {
+            final ClickableSpan span = getSpanForOffset(virtualViewId);
+            if (span != null) {
+                info.setContentDescription(getTextForSpan(span));
+            } else {
+                Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+                info.setContentDescription(mView.getText());
             }
+            info.setFocusable(true);
+            info.setClickable(true);
+            getBoundsForSpan(span, mTempRect);
+            if (mTempRect.isEmpty()) {
+                Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
+                mTempRect.set(0, 0, 1, 1);
+            }
+            info.setBoundsInParent(mTempRect);
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
         }
-        return null;
-    }
 
-    private CharSequence getTextForSpan(ClickableSpan span) {
-        CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            Spanned spannedText = (Spanned) text;
-            return spannedText.subSequence(spannedText.getSpanStart(span),
-                    spannedText.getSpanEnd(span));
-        }
-        return text;
-    }
-
-    // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for the
-    // section on the first line.
-    private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
-        CharSequence text = mView.getText();
-        outRect.setEmpty();
-        if (text instanceof Spanned) {
-            final Layout layout = mView.getLayout();
-            if (layout != null) {
-                Spanned spannedText = (Spanned) text;
-                final int spanStart = spannedText.getSpanStart(span);
-                final int spanEnd = spannedText.getSpanEnd(span);
-                final float xStart = layout.getPrimaryHorizontal(spanStart);
-                final float xEnd = layout.getPrimaryHorizontal(spanEnd);
-                final int lineStart = layout.getLineForOffset(spanStart);
-                final int lineEnd = layout.getLineForOffset(spanEnd);
-                layout.getLineBounds(lineStart, outRect);
-                if (lineEnd == lineStart) {
-                    // If the span is on a single line, adjust both the left and right bounds
-                    // so outrect is exactly bounding the span.
-                    outRect.left = (int) Math.min(xStart, xEnd);
-                    outRect.right = (int) Math.max(xStart, xEnd);
+        protected boolean onPerformActionForVirtualView(
+                int virtualViewId,
+                int action,
+                Bundle arguments) {
+            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+                ClickableSpan span = getSpanForOffset(virtualViewId);
+                if (span != null) {
+                    span.onClick(mView);
+                    return true;
                 } else {
-                    // If the span wraps across multiple lines, only use the first line (as returned
-                    // by layout.getLineBounds above), and adjust the "start" of outrect to where
-                    // the span starts, leaving the "end" of outrect at the end of the line.
-                    // ("start" being left for LTR, and right for RTL)
-                    if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
-                        outRect.right = (int) xStart;
-                    } else {
-                        outRect.left = (int) xStart;
-                    }
+                    Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
                 }
-
-                // Offset for padding
-                outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
             }
+            return false;
         }
-        return outRect;
-    }
 
-    // Compat implementation of TextView#getOffsetForPosition().
+        private ClickableSpan getSpanForOffset(int offset) {
+            CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                Spanned spannedText = (Spanned) text;
+                ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+                if (spans.length == 1) {
+                    return spans[0];
+                }
+            }
+            return null;
+        }
 
-    private static int getOffsetForPosition(TextView view, float x, float y) {
-        if (view.getLayout() == null) return -1;
-        final int line = getLineAtCoordinate(view, y);
-        return getOffsetAtCoordinate(view, line, x);
-    }
+        private CharSequence getTextForSpan(ClickableSpan span) {
+            CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                Spanned spannedText = (Spanned) text;
+                return spannedText.subSequence(
+                        spannedText.getSpanStart(span),
+                        spannedText.getSpanEnd(span));
+            }
+            return text;
+        }
 
-    private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
-        x -= view.getTotalPaddingLeft();
-        // Clamp the position to inside of the view.
-        x = Math.max(0.0f, x);
-        x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
-        x += view.getScrollX();
-        return x;
-    }
+        // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for
+        // the section on the first line.
+        private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
+            CharSequence text = mView.getText();
+            outRect.setEmpty();
+            if (text instanceof Spanned) {
+                final Layout layout = mView.getLayout();
+                if (layout != null) {
+                    Spanned spannedText = (Spanned) text;
+                    final int spanStart = spannedText.getSpanStart(span);
+                    final int spanEnd = spannedText.getSpanEnd(span);
+                    final float xStart = layout.getPrimaryHorizontal(spanStart);
+                    final float xEnd = layout.getPrimaryHorizontal(spanEnd);
+                    final int lineStart = layout.getLineForOffset(spanStart);
+                    final int lineEnd = layout.getLineForOffset(spanEnd);
+                    layout.getLineBounds(lineStart, outRect);
+                    if (lineEnd == lineStart) {
+                        // If the span is on a single line, adjust both the left and right bounds
+                        // so outrect is exactly bounding the span.
+                        outRect.left = (int) Math.min(xStart, xEnd);
+                        outRect.right = (int) Math.max(xStart, xEnd);
+                    } else {
+                        // If the span wraps across multiple lines, only use the first line (as
+                        // returned by layout.getLineBounds above), and adjust the "start" of
+                        // outrect to where the span starts, leaving the "end" of outrect at the end
+                        // of the line. ("start" being left for LTR, and right for RTL)
+                        if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
+                            outRect.right = (int) xStart;
+                        } else {
+                            outRect.left = (int) xStart;
+                        }
+                    }
 
-    private static int getLineAtCoordinate(TextView view, float y) {
-        y -= view.getTotalPaddingTop();
-        // Clamp the position to inside of the view.
-        y = Math.max(0.0f, y);
-        y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
-        y += view.getScrollY();
-        return view.getLayout().getLineForVertical((int) y);
-    }
+                    // Offset for padding
+                    outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
+                }
+            }
+            return outRect;
+        }
 
-    private static int getOffsetAtCoordinate(TextView view, int line, float x) {
-        x = convertToLocalHorizontalCoordinate(view, x);
-        return view.getLayout().getOffsetForHorizontal(line, x);
+        // Compat implementation of TextView#getOffsetForPosition().
+
+        private static int getOffsetForPosition(TextView view, float x, float y) {
+            if (view.getLayout() == null) return -1;
+            final int line = getLineAtCoordinate(view, y);
+            return getOffsetAtCoordinate(view, line, x);
+        }
+
+        private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
+            x -= view.getTotalPaddingLeft();
+            // Clamp the position to inside of the view.
+            x = Math.max(0.0f, x);
+            x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
+            x += view.getScrollX();
+            return x;
+        }
+
+        private static int getLineAtCoordinate(TextView view, float y) {
+            y -= view.getTotalPaddingTop();
+            // Clamp the position to inside of the view.
+            y = Math.max(0.0f, y);
+            y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
+            y += view.getScrollY();
+            return view.getLayout().getLineForVertical((int) y);
+        }
+
+        private static int getOffsetAtCoordinate(TextView view, int line, float x) {
+            x = convertToLocalHorizontalCoordinate(view, x);
+            return view.getLayout().getOffsetForHorizontal(line, x);
+        }
     }
 }
diff --git a/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java b/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
similarity index 75%
rename from com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
rename to com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
index 844e73e..6228e6f 100644
--- a/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
+++ b/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
@@ -14,29 +14,35 @@
  * limitations under the License.
  */
 
-package com.android.setupwizardlib.test;
+package com.android.setupwizardlib.util;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.graphics.Rect;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.text.BidiFormatter;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
 import android.support.v4.widget.ExploreByTouchHelper;
 import android.text.SpannableStringBuilder;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.setupwizardlib.span.LinkSpan;
-import com.android.setupwizardlib.util.LinkAccessibilityHelper;
+import com.android.setupwizardlib.util.LinkAccessibilityHelper.PreOLinkAccessibilityHelper;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,13 +58,12 @@
     private static final LinkSpan LINK_SPAN = new LinkSpan("foobar");
 
     private TextView mTextView;
-    private TestLinkAccessibilityHelper mHelper;
+    private TestPreOLinkAccessibilityHelper mHelper;
 
     private DisplayMetrics mDisplayMetrics;
 
     @Test
     public void testGetVirtualViewAt() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10));
         assertEquals("Virtual view ID should be 1", 1, virtualViewId);
@@ -66,7 +71,6 @@
 
     @Test
     public void testGetVirtualViewAtHost() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100));
         assertEquals("Virtual view ID should be INVALID_ID",
@@ -75,7 +79,6 @@
 
     @Test
     public void testGetVisibleVirtualViews() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         List<Integer> virtualViewIds = new ArrayList<>();
         mHelper.getVisibleVirtualViews(virtualViewIds);
@@ -86,7 +89,6 @@
 
     @Test
     public void testOnPopulateEventForVirtualView() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         AccessibilityEvent event = AccessibilityEvent.obtain();
         mHelper.onPopulateEventForVirtualView(1, event);
@@ -100,7 +102,6 @@
 
     @Test
     public void testOnPopulateEventForVirtualViewHost() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         AccessibilityEvent event = AccessibilityEvent.obtain();
         mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event);
@@ -113,7 +114,6 @@
 
     @Test
     public void testOnPopulateNodeForVirtualView() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
         mHelper.onPopulateNodeForVirtualView(1, info);
@@ -132,7 +132,6 @@
 
     @Test
     public void testNullLayout() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         // Setting the padding will cause the layout to be null-ed out.
         mTextView.setPadding(1, 1, 1, 1);
@@ -150,7 +149,6 @@
 
     @Test
     public void testRtlLayout() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום");
         ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
         initTextView(ssb);
@@ -170,7 +168,6 @@
 
     @Test
     public void testMultilineLink() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         SpannableStringBuilder ssb = new SpannableStringBuilder(
                 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
                 + "Praesent accumsan efficitur eros eu porttitor.");
@@ -192,7 +189,6 @@
 
     @Test
     public void testRtlMultilineLink() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
                 + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
                 + "דפים המחשב מיזמים ב.";
@@ -216,7 +212,6 @@
 
     @Test
     public void testBidiMultilineLink() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
                 + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
                 + "דפים המחשב מיזמים ב.";
@@ -243,6 +238,70 @@
         info.recycle();
     }
 
+    @Test
+    public void testMethodDelegation() {
+        initTextView();
+        ExploreByTouchHelper delegate = mock(TestPreOLinkAccessibilityHelper.class);
+        LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate);
+
+        AccessibilityEvent accessibilityEvent =
+                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED);
+
+        helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED);
+        verify(delegate).sendAccessibilityEvent(
+                same(mTextView),
+                eq(AccessibilityEvent.TYPE_VIEW_CLICKED));
+
+        helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent);
+        verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent));
+
+        helper.performAccessibilityAction(
+                mTextView,
+                AccessibilityActionCompat.ACTION_CLICK.getId(),
+                Bundle.EMPTY);
+        verify(delegate).performAccessibilityAction(
+                same(mTextView),
+                eq(AccessibilityActionCompat.ACTION_CLICK.getId()),
+                eq(Bundle.EMPTY));
+
+        helper.dispatchPopulateAccessibilityEvent(
+                mTextView,
+                accessibilityEvent);
+        verify(delegate).dispatchPopulateAccessibilityEvent(
+                same(mTextView),
+                same(accessibilityEvent));
+
+        MotionEvent motionEvent = MotionEvent.obtain(0, 0, 0, 0, 0, 0);
+        helper.dispatchHoverEvent(motionEvent);
+        verify(delegate).dispatchHoverEvent(eq(motionEvent));
+
+        helper.getAccessibilityNodeProvider(mTextView);
+        verify(delegate).getAccessibilityNodeProvider(same(mTextView));
+
+        helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent);
+        verify(delegate).onInitializeAccessibilityEvent(
+                same(mTextView),
+                eq(accessibilityEvent));
+
+        AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain();
+        helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo);
+        verify(delegate).onInitializeAccessibilityNodeInfo(
+                same(mTextView),
+                same(accessibilityNodeInfo));
+
+        helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent);
+        verify(delegate).onPopulateAccessibilityEvent(
+                same(mTextView),
+                same(accessibilityEvent));
+
+        FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext());
+        helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent);
+        verify(delegate).onRequestSendAccessibilityEvent(
+                same(parent),
+                same(mTextView),
+                same(accessibilityEvent));
+    }
+
     private void initTextView() {
         SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world");
         ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
@@ -254,7 +313,7 @@
         mTextView.setSingleLine(false);
         mTextView.setText(text);
         mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
-        mHelper = new TestLinkAccessibilityHelper(mTextView);
+        mHelper = new TestPreOLinkAccessibilityHelper(mTextView);
 
         int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500),
                 View.MeasureSpec.EXACTLY);
@@ -270,9 +329,9 @@
         return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics);
     }
 
-    private static class TestLinkAccessibilityHelper extends LinkAccessibilityHelper {
+    public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper {
 
-        TestLinkAccessibilityHelper(TextView view) {
+        TestPreOLinkAccessibilityHelper(TextView view) {
             super(view);
         }
 
diff --git a/com/android/setupwizardlib/util/PartnerTest.java b/com/android/setupwizardlib/util/PartnerTest.java
index f47eef1..aeb678f 100644
--- a/com/android/setupwizardlib/util/PartnerTest.java
+++ b/com/android/setupwizardlib/util/PartnerTest.java
@@ -31,6 +31,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Build.VERSION;
@@ -40,20 +41,25 @@
 import com.android.setupwizardlib.R;
 import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner;
 import com.android.setupwizardlib.util.Partner.ResourceEntry;
+import com.android.setupwizardlib.util.PartnerTest.ShadowApplicationPackageManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
 import org.robolectric.annotation.Config;
-import org.robolectric.res.builder.DefaultPackageManager;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowResources;
 
 import java.util.Arrays;
 import java.util.Collections;
 
 @RunWith(SuwLibRobolectricTestRunner.class)
-@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK })
+@Config(
+        constants = BuildConfig.class,
+        sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK },
+        shadows = ShadowApplicationPackageManager.class)
 public class PartnerTest {
 
     private static final String ACTION_PARTNER_CUSTOMIZATION =
@@ -62,7 +68,7 @@
     private Context mContext;
     private Resources mPartnerResources;
 
-    private TestPackageManager mPackageManager;
+    private ShadowApplicationPackageManager mPackageManager;
 
     @Before
     public void setUp() throws Exception {
@@ -71,8 +77,9 @@
         mContext = spy(application);
         mPartnerResources = spy(ShadowResources.getSystem());
 
-        mPackageManager = new TestPackageManager();
-        RuntimeEnvironment.setRobolectricPackageManager(mPackageManager);
+        mPackageManager =
+                (ShadowApplicationPackageManager) Shadows.shadowOf(application.getPackageManager());
+        mPackageManager.partnerResources = mPartnerResources;
     }
 
     @Test
@@ -173,13 +180,18 @@
         return info;
     }
 
-    private class TestPackageManager extends DefaultPackageManager {
+    @Implements(className = "android.app.ApplicationPackageManager")
+    public static class ShadowApplicationPackageManager extends
+            org.robolectric.shadows.ShadowApplicationPackageManager {
 
+        public Resources partnerResources;
+
+        @Implementation
         @Override
         public Resources getResourcesForApplication(ApplicationInfo app)
                 throws NameNotFoundException {
             if (app != null && "test.partner.package".equals(app.packageName)) {
-                return mPartnerResources;
+                return partnerResources;
             } else {
                 return super.getResourcesForApplication(app);
             }
diff --git a/com/android/setupwizardlib/view/IllustrationVideoViewTest.java b/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
index ffa228d..ddf59ca 100644
--- a/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
+++ b/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
@@ -48,7 +48,7 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
-import org.robolectric.internal.Shadow;
+import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowMediaPlayer;
 import org.robolectric.util.ReflectionHelpers;
 
diff --git a/com/android/setupwizardlib/view/NavigationBarButton.java b/com/android/setupwizardlib/view/NavigationBarButton.java
index 5172c47..45d3737 100644
--- a/com/android/setupwizardlib/view/NavigationBarButton.java
+++ b/com/android/setupwizardlib/view/NavigationBarButton.java
@@ -17,143 +17,16 @@
 package com.android.setupwizardlib.view;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.widget.Button;
 
-/**
- * Button for navigation bar, which includes tinting of its compound drawables to be used for dark
- * and light themes.
- */
 public class NavigationBarButton extends Button {
 
     public NavigationBarButton(Context context) {
         super(context);
-        init();
     }
 
     public NavigationBarButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
-    }
-
-    private void init() {
-        // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter,
-        // so manually getting it and wrapping it in the compat drawable.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Drawable[] drawables = getCompoundDrawablesRelative();
-            for (int i = 0; i < drawables.length; i++) {
-                if (drawables[i] != null) {
-                    drawables[i] = TintedDrawable.wrap(drawables[i]);
-                }
-            }
-            setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1],
-                    drawables[2], drawables[3]);
-        }
-    }
-
-    @Override
-    public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
-        if (left != null) left = TintedDrawable.wrap(left);
-        if (top != null) top = TintedDrawable.wrap(top);
-        if (right != null) right = TintedDrawable.wrap(right);
-        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
-        super.setCompoundDrawables(left, top, right, bottom);
-        tintDrawables();
-    }
-
-    @Override
-    public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end,
-            Drawable bottom) {
-        if (start != null) start = TintedDrawable.wrap(start);
-        if (top != null) top = TintedDrawable.wrap(top);
-        if (end != null) end = TintedDrawable.wrap(end);
-        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
-        super.setCompoundDrawablesRelative(start, top, end, bottom);
-        tintDrawables();
-    }
-
-    @Override
-    public void setTextColor(ColorStateList colors) {
-        super.setTextColor(colors);
-        tintDrawables();
-    }
-
-    private void tintDrawables() {
-        final ColorStateList textColors = getTextColors();
-        if (textColors != null) {
-            for (Drawable drawable : getAllCompoundDrawables()) {
-                if (drawable instanceof TintedDrawable) {
-                    ((TintedDrawable) drawable).setTintListCompat(textColors);
-                }
-            }
-            invalidate();
-        }
-    }
-
-    private Drawable[] getAllCompoundDrawables() {
-        Drawable[] drawables = new Drawable[6];
-        Drawable[] compoundDrawables = getCompoundDrawables();
-        drawables[0] = compoundDrawables[0];  // left
-        drawables[1] = compoundDrawables[1];  // top
-        drawables[2] = compoundDrawables[2];  // right
-        drawables[3] = compoundDrawables[3];  // bottom
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative();
-            drawables[4] = compoundDrawablesRelative[0];  // start
-            drawables[5] = compoundDrawablesRelative[2];  // end
-        }
-        return drawables;
-    }
-
-    // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0
-    // or above
-    private static class TintedDrawable extends LayerDrawable {
-
-        public static TintedDrawable wrap(Drawable drawable) {
-            if (drawable instanceof TintedDrawable) {
-                return (TintedDrawable) drawable;
-            }
-            return new TintedDrawable(drawable.mutate());
-        }
-
-        private ColorStateList mTintList = null;
-
-        TintedDrawable(Drawable wrapped) {
-            super(new Drawable[] { wrapped });
-        }
-
-        @Override
-        public boolean isStateful() {
-            return true;
-        }
-
-        @Override
-        public boolean setState(@NonNull int[] stateSet) {
-            boolean needsInvalidate = super.setState(stateSet);
-            boolean needsInvalidateForState = updateState();
-            return needsInvalidate || needsInvalidateForState;
-        }
-
-        public void setTintListCompat(ColorStateList colors) {
-            mTintList = colors;
-            if (updateState()) {
-                invalidateSelf();
-            }
-        }
-
-        private boolean updateState() {
-            if (mTintList != null) {
-                final int color = mTintList.getColorForState(getState(), 0);
-                setColorFilter(color, PorterDuff.Mode.SRC_IN);
-                return true;  // Needs invalidate
-            }
-            return false;
-        }
     }
 }
diff --git a/com/android/setupwizardlib/view/RichTextView.java b/com/android/setupwizardlib/view/RichTextView.java
index e6bc9da..5a78561 100644
--- a/com/android/setupwizardlib/view/RichTextView.java
+++ b/com/android/setupwizardlib/view/RichTextView.java
@@ -17,11 +17,6 @@
 package com.android.setupwizardlib.view;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.AppCompatTextView;
 import android.text.Annotation;
 import android.text.SpannableString;
 import android.text.Spanned;
@@ -30,18 +25,22 @@
 import android.text.style.TextAppearanceSpan;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.MotionEvent;
+import android.widget.TextView;
 
 import com.android.setupwizardlib.span.LinkSpan;
 import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener;
 import com.android.setupwizardlib.span.SpanHelper;
-import com.android.setupwizardlib.util.LinkAccessibilityHelper;
 
 /**
  * An extension of TextView that automatically replaces the annotation tags as specified in
  * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
+ *
+ * <p>Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built
+ * into platform in O, although the interaction paradigm is different. (See b/17726921). In this
+ * platform version, the links are exposed in the Local Context Menu of TalkBack instead of
+ * accessible directly through swiping.
  */
-public class RichTextView extends AppCompatTextView implements OnLinkClickListener {
+public class RichTextView extends TextView implements OnLinkClickListener {
 
     /* static section */
 
@@ -89,22 +88,14 @@
 
     /* non-static section */
 
-    private LinkAccessibilityHelper mAccessibilityHelper;
     private OnLinkClickListener mOnLinkClickListener;
 
     public RichTextView(Context context) {
         super(context);
-        init();
     }
 
     public RichTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
-    }
-
-    private void init() {
-        mAccessibilityHelper = new LinkAccessibilityHelper(this);
-        ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
     }
 
     @Override
@@ -141,32 +132,6 @@
         return false;
     }
 
-    @Override
-    protected boolean dispatchHoverEvent(MotionEvent event) {
-        if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
-            return true;
-        }
-        return super.dispatchHoverEvent(event);
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-
-        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
-            // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a
-            // workaround, set the state on those drawables directly.
-            final int[] state = getDrawableState();
-            for (Drawable drawable : getCompoundDrawablesRelative()) {
-                if (drawable != null) {
-                    if (drawable.setState(state)) {
-                        invalidateDrawable(drawable);
-                    }
-                }
-            }
-        }
-    }
-
     public void setOnLinkClickListener(OnLinkClickListener listener) {
         mOnLinkClickListener = listener;
     }
diff --git a/com/android/systemui/BatteryMeterView.java b/com/android/systemui/BatteryMeterView.java
index 2b31967..2fe66a1 100644
--- a/com/android/systemui/BatteryMeterView.java
+++ b/com/android/systemui/BatteryMeterView.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui;
 
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
 
 import android.animation.ArgbEvaluator;
@@ -52,6 +54,7 @@
 import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 import java.text.NumberFormat;
 
@@ -101,6 +104,9 @@
 
         mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
 
+        addOnAttachStateChangeListener(
+                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
+
         mSlotBattery = context.getString(
                 com.android.internal.R.string.status_bar_battery);
         mBatteryIconView = new ImageView(context);
diff --git a/com/android/systemui/assist/AssistManager.java b/com/android/systemui/assist/AssistManager.java
index c5eebcc..8a8bafa 100644
--- a/com/android/systemui/assist/AssistManager.java
+++ b/com/android/systemui/assist/AssistManager.java
@@ -61,6 +61,7 @@
     private AssistOrbContainer mView;
     private final DeviceProvisionedController mDeviceProvisionedController;
     protected final AssistUtils mAssistUtils;
+    private final boolean mShouldEnableOrb;
 
     private IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
@@ -96,6 +97,7 @@
                 | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
                 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
         onConfigurationChanged(context.getResources().getConfiguration());
+        mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
     }
 
     protected void registerVoiceInteractionSessionListener() {
@@ -179,7 +181,9 @@
 
     private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
         maybeSwapSearchIcon(assistComponent, isService);
-        mView.show(true /* show */, true /* animate */);
+        if (mShouldEnableOrb) {
+            mView.show(true /* show */, true /* animate */);
+        }
     }
 
     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
diff --git a/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index 5c99961..debda21 100644
--- a/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.doze;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.util.KeyValueListParser;
@@ -34,6 +39,10 @@
 public class AlwaysOnDisplayPolicy {
     public static final String TAG = "AlwaysOnDisplayPolicy";
 
+    private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+
     static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
     static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
     static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
@@ -46,7 +55,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_SCREEN_BRIGHTNESS_ARRAY
      */
-    public final int[] screenBrightnessArray;
+    public int[] screenBrightnessArray;
 
     /**
      * Integer array to map ambient brightness type to dimming scrim.
@@ -54,7 +63,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_DIMMING_SCRIM_ARRAY
      */
-    public final int[] dimmingScrimArray;
+    public int[] dimmingScrimArray;
 
     /**
      * Delay time(ms) from covering the prox to turning off the screen.
@@ -62,7 +71,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_SCREEN_OFF_DELAY_MS
      */
-    public final long proxScreenOffDelayMs;
+    public long proxScreenOffDelayMs;
 
     /**
      * The threshold time(ms) to trigger the cooldown timer, which will
@@ -71,7 +80,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_TRIGGER_MS
      */
-    public final long proxCooldownTriggerMs;
+    public long proxCooldownTriggerMs;
 
     /**
      * The period(ms) to turning off the prox sensor if
@@ -80,43 +89,78 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_PERIOD_MS
      */
-    public final long proxCooldownPeriodMs;
+    public long proxCooldownPeriodMs;
 
     private final KeyValueListParser mParser;
+    private final Context mContext;
+    private SettingsObserver mSettingsObserver;
 
     public AlwaysOnDisplayPolicy(Context context) {
-        final Resources resources = context.getResources();
+        mContext = context;
         mParser = new KeyValueListParser(',');
-
-        final String value = Settings.Global.getString(context.getContentResolver(),
-                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
-
-        try {
-            mParser.setString(value);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Bad AOD constants");
-        }
-
-        proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
-                10 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
-                2 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
-                5 * DateUtils.SECOND_IN_MILLIS);
-        screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness));
-        dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity));
+        mSettingsObserver = new SettingsObserver(context.getMainThreadHandler());
+        mSettingsObserver.observe();
     }
 
     private int[] parseIntArray(final String key, final int[] defaultArray) {
         final String value = mParser.getString(key, null);
         if (value != null) {
-            return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
-                    Integer::parseInt).toArray();
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
         } else {
             return defaultArray;
         }
     }
 
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri ALWAYS_ON_DISPLAY_CONSTANTS_URI
+                = Settings.Global.getUriFor(Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void observe() {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI,
+                    false, this, UserHandle.USER_ALL);
+            update(null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            update(uri);
+        }
+
+        public void update(Uri uri) {
+            if (uri == null || ALWAYS_ON_DISPLAY_CONSTANTS_URI.equals(uri)) {
+                final Resources resources = mContext.getResources();
+                final String value = Settings.Global.getString(mContext.getContentResolver(),
+                        Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Bad AOD constants");
+                }
+
+                proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
+                        DEFAULT_PROX_SCREEN_OFF_DELAY_MS);
+                proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
+                        DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
+                proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
+                        DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+                screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_brightness));
+                dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_scrim_opacity));
+            }
+        }
+    }
 }
diff --git a/com/android/systemui/doze/DozePauser.java b/com/android/systemui/doze/DozePauser.java
index 76a1902..58f1448 100644
--- a/com/android/systemui/doze/DozePauser.java
+++ b/com/android/systemui/doze/DozePauser.java
@@ -28,20 +28,21 @@
     public static final String TAG = DozePauser.class.getSimpleName();
     private final AlarmTimeout mPauseTimeout;
     private final DozeMachine mMachine;
-    private final long mTimeoutMs;
+    private final AlwaysOnDisplayPolicy mPolicy;
 
     public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager,
             AlwaysOnDisplayPolicy policy) {
         mMachine = machine;
         mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler);
-        mTimeoutMs = policy.proxScreenOffDelayMs;
+        mPolicy = policy;
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case DOZE_AOD_PAUSING:
-                mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+                mPauseTimeout.schedule(mPolicy.proxScreenOffDelayMs,
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
                 break;
             default:
                 mPauseTimeout.cancel();
diff --git a/com/android/systemui/doze/DozeScreenBrightness.java b/com/android/systemui/doze/DozeScreenBrightness.java
index 03407e2..4bb4e79 100644
--- a/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/com/android/systemui/doze/DozeScreenBrightness.java
@@ -22,6 +22,7 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.os.Handler;
+import android.os.Trace;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -94,9 +95,14 @@
 
     @Override
     public void onSensorChanged(SensorEvent event) {
-        if (mRegistered) {
-            mLastSensorValue = (int) event.values[0];
-            updateBrightnessAndReady();
+        Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+        try {
+            if (mRegistered) {
+                mLastSensorValue = (int) event.values[0];
+                updateBrightnessAndReady();
+            }
+        } finally {
+            Trace.endSection();
         }
     }
 
diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java
index 4cbbbd6..189badf 100644
--- a/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1280,7 +1280,23 @@
             mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
             GradientColors colors = mColorExtractor.getColors(mKeyguardShowing ?
                     WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM);
-            mGradientDrawable.setColors(colors, false);
+            updateColors(colors, false /* animate */);
+        }
+
+        /**
+         * Updates background and system bars according to current GradientColors.
+         * @param colors Colors and hints to use.
+         * @param animate Interpolates gradient if true, just sets otherwise.
+         */
+        private void updateColors(GradientColors colors, boolean animate) {
+            mGradientDrawable.setColors(colors, animate);
+            View decorView = getWindow().getDecorView();
+            if (colors.supportsDarkText()) {
+                decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
+                    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+            } else {
+                decorView.setSystemUiVisibility(0);
+            }
         }
 
         @Override
@@ -1350,11 +1366,13 @@
         public void onColorsChanged(ColorExtractor extractor, int which) {
             if (mKeyguardShowing) {
                 if ((WallpaperManager.FLAG_LOCK & which) != 0) {
-                    mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
+                    updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
+                            true /* animate */);
                 }
             } else {
                 if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
-                    mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+                    updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
+                            true /* animate */);
                 }
             }
         }
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3eb68f5..28adca9 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard;
 
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.telephony.IccCardConstants.State.ABSENT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -239,6 +240,9 @@
     // answer whether the input should be restricted)
     private boolean mShowing;
 
+    // display id of the secondary display on which we have put a keyguard window
+    private int mSecondaryDisplayShowing = INVALID_DISPLAY;
+
     /** Cached value of #isInputRestricted */
     private boolean mInputRestricted;
 
@@ -646,6 +650,13 @@
             }
             return KeyguardSecurityView.PROMPT_REASON_NONE;
         }
+
+        @Override
+        public void onSecondaryDisplayShowingChanged(int displayId) {
+            synchronized (KeyguardViewMediator.this) {
+                setShowingLocked(mShowing, displayId, false);
+            }
+        }
     };
 
     public void userActivity() {
@@ -670,7 +681,7 @@
         filter.addAction(Intent.ACTION_SHUTDOWN);
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
-        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext);
+        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext, mViewMediatorCallback);
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
@@ -685,7 +696,8 @@
                 com.android.keyguard.R.bool.config_enableKeyguardService)) {
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
+                            KeyguardUpdateMonitor.getCurrentUser()),
+                    mSecondaryDisplayShowing, true /* forceCallbacks */);
         }
 
         mStatusBarKeyguardViewManager =
@@ -1694,10 +1706,10 @@
         playSound(mTrustedSoundId);
     }
 
-    private void updateActivityLockScreenState(boolean showing) {
+    private void updateActivityLockScreenState(boolean showing, int secondaryDisplayShowing) {
         mUiOffloadThread.submit(() -> {
             try {
-                ActivityManager.getService().setLockScreenShown(showing);
+                ActivityManager.getService().setLockScreenShown(showing, secondaryDisplayShowing);
             } catch (RemoteException e) {
             }
         });
@@ -2060,30 +2072,39 @@
     }
 
     private void setShowingLocked(boolean showing) {
-        setShowingLocked(showing, false /* forceCallbacks */);
+        setShowingLocked(showing, mSecondaryDisplayShowing, false /* forceCallbacks */);
     }
 
-    private void setShowingLocked(boolean showing, boolean forceCallbacks) {
-        if (showing != mShowing || forceCallbacks) {
+    private void setShowingLocked(
+            boolean showing, int secondaryDisplayShowing, boolean forceCallbacks) {
+        final boolean notifyDefaultDisplayCallbacks = showing != mShowing || forceCallbacks;
+        if (notifyDefaultDisplayCallbacks || secondaryDisplayShowing != mSecondaryDisplayShowing) {
             mShowing = showing;
-            int size = mKeyguardStateCallbacks.size();
-            for (int i = size - 1; i >= 0; i--) {
-                IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
-                try {
-                    callback.onShowingStateChanged(showing);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onShowingStateChanged", e);
-                    if (e instanceof DeadObjectException) {
-                        mKeyguardStateCallbacks.remove(callback);
-                    }
+            mSecondaryDisplayShowing = secondaryDisplayShowing;
+            if (notifyDefaultDisplayCallbacks) {
+                notifyDefaultDisplayCallbacks(showing);
+            }
+            updateActivityLockScreenState(showing, secondaryDisplayShowing);
+        }
+    }
+
+    private void notifyDefaultDisplayCallbacks(boolean showing) {
+        int size = mKeyguardStateCallbacks.size();
+        for (int i = size - 1; i >= 0; i--) {
+            IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
+            try {
+                callback.onShowingStateChanged(showing);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to call onShowingStateChanged", e);
+                if (e instanceof DeadObjectException) {
+                    mKeyguardStateCallbacks.remove(callback);
                 }
             }
-            updateInputRestrictedLocked();
-            mUiOffloadThread.submit(() -> {
-                mTrustManager.reportKeyguardShowingChanged();
-            });
-            updateActivityLockScreenState(showing);
         }
+        updateInputRestrictedLocked();
+        mUiOffloadThread.submit(() -> {
+            mTrustManager.reportKeyguardShowingChanged();
+        });
     }
 
     private void notifyTrustedChangedLocked(boolean trusted) {
diff --git a/com/android/systemui/media/NotificationPlayer.java b/com/android/systemui/media/NotificationPlayer.java
index 50720e9..b5c0d53 100644
--- a/com/android/systemui/media/NotificationPlayer.java
+++ b/com/android/systemui/media/NotificationPlayer.java
@@ -29,6 +29,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.LinkedList;
 
 /**
@@ -57,8 +59,12 @@
         }
     }
 
-    private LinkedList<Command> mCmdQueue = new LinkedList();
+    private final LinkedList<Command> mCmdQueue = new LinkedList<Command>();
 
+    private final Object mCompletionHandlingLock = new Object();
+    @GuardedBy("mCompletionHandlingLock")
+    private CreationAndCompletionThread mCompletionThread;
+    @GuardedBy("mCompletionHandlingLock")
     private Looper mLooper;
 
     /*
@@ -76,7 +82,10 @@
 
         public void run() {
             Looper.prepare();
+            // ok to modify mLooper as here we are
+            // synchronized on mCompletionHandlingLock due to the Object.wait() in startSound(cmd)
             mLooper = Looper.myLooper();
+            if (DEBUG) Log.d(mTag, "in run: new looper " + mLooper);
             synchronized(this) {
                 AudioManager audioManager =
                     (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
@@ -97,7 +106,7 @@
                     if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
                             && (mCmd.uri.getEncodedPath().length() > 0)) {
                         if (!audioManager.isMusicActiveRemotely()) {
-                            synchronized(mQueueAudioFocusLock) {
+                            synchronized (mQueueAudioFocusLock) {
                                 if (mAudioManagerWithAudioFocus == null) {
                                     if (DEBUG) Log.d(mTag, "requesting AudioFocus");
                                     int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
@@ -129,7 +138,9 @@
                         Log.e(mTag, "Exception while sleeping to sync notification playback"
                                 + " with ducking", e);
                     }
+                    if (DEBUG) { Log.d(mTag, "player.start"); }
                     if (mPlayer != null) {
+                        if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
                         mPlayer.release();
                     }
                     mPlayer = player;
@@ -148,7 +159,7 @@
         // is playing, let it continue until we're done, so there
         // is less of a glitch.
         try {
-            if (DEBUG) Log.d(mTag, "Starting playback");
+            if (DEBUG) { Log.d(mTag, "startSound()"); }
             //-----------------------------------
             // This is were we deviate from the AsyncPlayer implementation and create the
             // MediaPlayer in a new thread with which we're synchronized
@@ -158,10 +169,11 @@
                 // matters
                 if((mLooper != null)
                         && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+                    if (DEBUG) { Log.d(mTag, "in startSound quitting looper " + mLooper); }
                     mLooper.quit();
                 }
                 mCompletionThread = new CreationAndCompletionThread(cmd);
-                synchronized(mCompletionThread) {
+                synchronized (mCompletionThread) {
                     mCompletionThread.start();
                     mCompletionThread.wait();
                 }
@@ -209,13 +221,18 @@
                         mPlayer = null;
                         synchronized(mQueueAudioFocusLock) {
                             if (mAudioManagerWithAudioFocus != null) {
+                                if (DEBUG) { Log.d(mTag, "in STOP: abandonning AudioFocus"); }
                                 mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                                 mAudioManagerWithAudioFocus = null;
                             }
                         }
-                        if((mLooper != null)
-                                && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
-                            mLooper.quit();
+                        synchronized (mCompletionHandlingLock) {
+                            if ((mLooper != null) &&
+                                    (mLooper.getThread().getState() != Thread.State.TERMINATED))
+                            {
+                                if (DEBUG) { Log.d(mTag, "in STOP: quitting looper "+ mLooper); }
+                                mLooper.quit();
+                            }
                         }
                     } else {
                         Log.w(mTag, "STOP command without a player");
@@ -250,9 +267,11 @@
         }
         // if there are no more sounds to play, end the Looper to listen for media completion
         synchronized (mCmdQueue) {
-            if (mCmdQueue.size() == 0) {
-                synchronized(mCompletionHandlingLock) {
-                    if(mLooper != null) {
+            synchronized(mCompletionHandlingLock) {
+                if (DEBUG) { Log.d(mTag, "onCompletion queue size=" + mCmdQueue.size()); }
+                if ((mCmdQueue.size() == 0)) {
+                    if (mLooper != null) {
+                        if (DEBUG) { Log.d(mTag, "in onCompletion quitting looper " + mLooper); }
                         mLooper.quit();
                     }
                     mCompletionThread = null;
@@ -269,13 +288,20 @@
     }
 
     private String mTag;
+
+    @GuardedBy("mCmdQueue")
     private CmdThread mThread;
-    private CreationAndCompletionThread mCompletionThread;
-    private final Object mCompletionHandlingLock = new Object();
+
     private MediaPlayer mPlayer;
+
+
+    @GuardedBy("mCmdQueue")
     private PowerManager.WakeLock mWakeLock;
+
     private final Object mQueueAudioFocusLock = new Object();
-    private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
+    @GuardedBy("mQueueAudioFocusLock")
+    private AudioManager mAudioManagerWithAudioFocus;
+
     private int mNotificationRampTimeMs = 0;
 
     // The current state according to the caller.  Reality lags behind
@@ -311,6 +337,7 @@
      */
     @Deprecated
     public void play(Context context, Uri uri, boolean looping, int stream) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
@@ -339,6 +366,7 @@
      *          (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
      */
     public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
         cmd.code = PLAY;
@@ -357,6 +385,7 @@
      * at this point.  Calling this multiple times has no ill effects.
      */
     public void stop() {
+        if (DEBUG) { Log.d(mTag, "stop"); }
         synchronized (mCmdQueue) {
             // This check allows stop to be called multiple times without starting
             // a thread that ends up doing nothing.
@@ -370,6 +399,7 @@
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void enqueueLocked(Command cmd) {
         mCmdQueue.add(cmd);
         if (mThread == null) {
@@ -393,22 +423,26 @@
      * @hide
      */
     public void setUsesWakeLock(Context context) {
-        if (mWakeLock != null || mThread != null) {
-            // if either of these has happened, we've already played something.
-            // and our releases will be out of sync.
-            throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
-                    + " mThread=" + mThread);
+        synchronized (mCmdQueue) {
+            if (mWakeLock != null || mThread != null) {
+                // if either of these has happened, we've already played something.
+                // and our releases will be out of sync.
+                throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+                        + " mThread=" + mThread);
+            }
+            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
         }
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
     }
 
+    @GuardedBy("mCmdQueue")
     private void acquireWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.acquire();
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void releaseWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.release();
diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java
index b3f992d..f8996aa 100644
--- a/com/android/systemui/pip/phone/PipManager.java
+++ b/com/android/systemui/pip/phone/PipManager.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
@@ -30,6 +31,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 import android.view.IWindowManager;
@@ -70,11 +72,11 @@
      */
     TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
-        public void onActivityPinned(String packageName, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             mTouchHandler.onActivityPinned();
             mMediaController.onActivityPinned();
             mMenuController.onActivityPinned();
-            mNotificationController.onActivityPinned(packageName,
+            mNotificationController.onActivityPinned(packageName, userId,
                     true /* deferUntilAnimationEnds */);
 
             SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
@@ -82,13 +84,15 @@
 
         @Override
         public void onActivityUnpinned() {
-            ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext,
-                    mActivityManager);
-            mMenuController.onActivityUnpinned(topPipActivity);
-            mTouchHandler.onActivityUnpinned(topPipActivity);
-            mNotificationController.onActivityUnpinned(topPipActivity);
+            final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPinnedActivity(
+                    mContext, mActivityManager);
+            final ComponentName topActivity = topPipActivityInfo.first;
+            final int userId = topActivity != null ? topPipActivityInfo.second : 0;
+            mMenuController.onActivityUnpinned();
+            mTouchHandler.onActivityUnpinned(topActivity);
+            mNotificationController.onActivityUnpinned(topActivity, userId);
 
-            SystemServicesProxy.getInstance(mContext).setPipVisibility(topPipActivity != null);
+            SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
         }
 
         @Override
@@ -196,7 +200,8 @@
     public final void onBusEvent(ExpandPipEvent event) {
         if (event.clearThumbnailWindows) {
             try {
-                StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                StackInfo stackInfo = mActivityManager.getStackInfo(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (stackInfo != null && stackInfo.taskIds != null) {
                     SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
                     for (int taskId : stackInfo.taskIds) {
diff --git a/com/android/systemui/pip/phone/PipMediaController.java b/com/android/systemui/pip/phone/PipMediaController.java
index b3a0794..174a7ef 100644
--- a/com/android/systemui/pip/phone/PipMediaController.java
+++ b/com/android/systemui/pip/phone/PipMediaController.java
@@ -230,7 +230,7 @@
     private void resolveActiveMediaController(List<MediaController> controllers) {
         if (controllers != null) {
             final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
-                    mActivityManager);
+                    mActivityManager).first;
             if (topActivity != null) {
                 for (int i = 0; i < controllers.size(); i++) {
                     final MediaController controller = controllers.get(i);
diff --git a/com/android/systemui/pip/phone/PipMenuActivityController.java b/com/android/systemui/pip/phone/PipMenuActivityController.java
index 34666fb..9fb201b 100644
--- a/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
@@ -223,7 +224,7 @@
         }
     }
 
-    public void onActivityUnpinned(ComponentName topPipActivity) {
+    public void onActivityUnpinned() {
         hideMenu();
         setStartActivityRequested(false);
     }
@@ -383,7 +384,8 @@
     private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds,
             boolean allowMenuTimeout, boolean willResizeMenu) {
         try {
-            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
                 Intent intent = new Intent(mContext, PipMenuActivity.class);
@@ -421,7 +423,8 @@
             // Fetch the pinned stack bounds
             Rect stackBounds = null;
             try {
-                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (pinnedStackInfo != null) {
                     stackBounds = pinnedStackInfo.bounds;
                 }
diff --git a/com/android/systemui/pip/phone/PipMotionHelper.java b/com/android/systemui/pip/phone/PipMotionHelper.java
index cebb22f..21a836c 100644
--- a/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_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_PINNED;
 import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
@@ -121,7 +123,8 @@
     void synchronizePinnedStackBounds() {
         cancelAnimations();
         try {
-            StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            StackInfo stackInfo =
+                    mActivityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (stackInfo != null) {
                 mBounds.set(stackInfo.bounds);
             }
@@ -158,13 +161,7 @@
         mMenuController.hideMenuWithoutResize();
         mHandler.post(() -> {
             try {
-                if (skipAnimation) {
-                    mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true /* onTop */);
-                } else {
-                    mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
-                            true /* allowResizeInDockedMode */, true /* preserveWindows */,
-                            true /* animate */, EXPAND_STACK_TO_FULLSCREEN_DURATION);
-                }
+                mActivityManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error expanding PiP activity", e);
             }
@@ -182,7 +179,7 @@
         mMenuController.hideMenuWithoutResize();
         mHandler.post(() -> {
             try {
-                mActivityManager.removeStack(PINNED_STACK_ID);
+                mActivityManager.removeStacksInWindowingModes(new int[]{ WINDOWING_MODE_PINNED });
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to remove PiP", e);
             }
@@ -529,14 +526,15 @@
                 Rect toBounds = (Rect) args.arg1;
                 int duration = args.argi1;
                 try {
-                    StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                    StackInfo stackInfo = mActivityManager.getStackInfo(
+                            WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                     if (stackInfo == null) {
                         // In the case where we've already re-expanded or dismissed the PiP, then
                         // just skip the resize
                         return true;
                     }
 
-                    mActivityManager.resizeStack(PINNED_STACK_ID, toBounds,
+                    mActivityManager.resizeStack(stackInfo.stackId, toBounds,
                             false /* allowResizeInDockedMode */, true /* preserveWindows */,
                             true /* animate */, duration);
                     mBounds.set(toBounds);
diff --git a/com/android/systemui/pip/phone/PipNotificationController.java b/com/android/systemui/pip/phone/PipNotificationController.java
index 696fdbc..6d083e9 100644
--- a/com/android/systemui/pip/phone/PipNotificationController.java
+++ b/com/android/systemui/pip/phone/PipNotificationController.java
@@ -35,10 +35,15 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.drawable.Icon;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
@@ -57,22 +62,29 @@
     private IActivityManager mActivityManager;
     private AppOpsManager mAppOpsManager;
     private NotificationManager mNotificationManager;
+    private IconDrawableFactory mIconDrawableFactory;
 
     private PipMotionHelper mMotionHelper;
 
     // Used when building a deferred notification
     private String mDeferredNotificationPackageName;
+    private int mDeferredNotificationUserId;
 
     private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
         @Override
         public void onOpChanged(String op, String packageName) {
             try {
                 // Dismiss the PiP once the user disables the app ops setting for that package
-                final ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
-                        packageName, 0);
-                if (mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, packageName)
-                        != MODE_ALLOWED) {
-                    mMotionHelper.dismissPip();
+                final Pair<ComponentName, Integer> topPipActivityInfo =
+                        PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+                if (topPipActivityInfo.first != null) {
+                    final ApplicationInfo appInfo = mContext.getPackageManager()
+                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
+                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
+                                mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
+                                        packageName) != MODE_ALLOWED) {
+                        mMotionHelper.dismissPip();
+                    }
                 }
             } catch (NameNotFoundException e) {
                 // Unregister the listener if the package can't be found
@@ -88,16 +100,18 @@
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mNotificationManager = NotificationManager.from(context);
         mMotionHelper = motionHelper;
+        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
     }
 
-    public void onActivityPinned(String packageName, boolean deferUntilAnimationEnds) {
+    public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
         // Clear any existing notification
         mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
 
         if (deferUntilAnimationEnds) {
             mDeferredNotificationPackageName = packageName;
+            mDeferredNotificationUserId = userId;
         } else {
-            showNotificationForApp(mDeferredNotificationPackageName);
+            showNotificationForApp(packageName, userId);
         }
 
         // Register for changes to the app ops setting for this package while it is in PiP
@@ -106,22 +120,25 @@
 
     public void onPinnedStackAnimationEnded() {
         if (mDeferredNotificationPackageName != null) {
-            showNotificationForApp(mDeferredNotificationPackageName);
+            showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
             mDeferredNotificationPackageName = null;
+            mDeferredNotificationUserId = 0;
         }
     }
 
-    public void onActivityUnpinned(ComponentName topPipActivity) {
+    public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
         // Unregister for changes to the previously PiP'ed package
         unregisterAppOpsListener();
 
         // Reset the deferred notification package
         mDeferredNotificationPackageName = null;
+        mDeferredNotificationUserId = 0;
 
         if (topPipActivity != null) {
             // onActivityUnpinned() is only called after the transition is complete, so we don't
             // need to defer until the animation ends to update the notification
-            onActivityPinned(topPipActivity.getPackageName(), false /* deferUntilAnimationEnds */);
+            onActivityPinned(topPipActivity.getPackageName(), userId,
+                    false /* deferUntilAnimationEnds */);
         } else {
             mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
         }
@@ -130,20 +147,27 @@
     /**
      * Builds and shows the notification for the given app.
      */
-    private void showNotificationForApp(String packageName) {
+    private void showNotificationForApp(String packageName, int userId) {
         // Build a new notification
-        final Notification.Builder builder =
-                new Notification.Builder(mContext, NotificationChannels.GENERAL)
-                        .setLocalOnly(true)
-                        .setOngoing(true)
-                        .setSmallIcon(R.drawable.pip_notification_icon)
-                        .setColor(mContext.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
-        if (updateNotificationForApp(builder, packageName)) {
-            SystemUI.overrideNotificationAppName(mContext, builder);
+        try {
+            final UserHandle user = UserHandle.of(userId);
+            final Context userContext = mContext.createPackageContextAsUser(
+                    mContext.getPackageName(), 0, user);
+            final Notification.Builder builder =
+                    new Notification.Builder(userContext, NotificationChannels.GENERAL)
+                            .setLocalOnly(true)
+                            .setOngoing(true)
+                            .setSmallIcon(R.drawable.pip_notification_icon)
+                            .setColor(mContext.getColor(
+                                    com.android.internal.R.color.system_notification_accent_color));
+            if (updateNotificationForApp(builder, packageName, user)) {
+                SystemUI.overrideNotificationAppName(mContext, builder);
 
-            // Show the new notification
-            mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+                // Show the new notification
+                mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+            }
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not show notification for application", e);
         }
     }
 
@@ -151,33 +175,33 @@
      * Updates the notification builder with app-specific information, returning whether it was
      * successful.
      */
-    private boolean updateNotificationForApp(Notification.Builder builder, String packageName) {
+    private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
+            UserHandle user) throws NameNotFoundException {
         final PackageManager pm = mContext.getPackageManager();
         final ApplicationInfo appInfo;
         try {
-            appInfo = pm.getApplicationInfo(packageName, 0);
+            appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
         } catch (NameNotFoundException e) {
             Log.e(TAG, "Could not update notification for application", e);
             return false;
         }
 
         if (appInfo != null) {
-            final String appName = pm.getApplicationLabel(appInfo).toString();
+            final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
+                    .toString();
             final String message = mContext.getString(R.string.pip_notification_message, appName);
             final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
                     Uri.fromParts("package", packageName, null));
+            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
             settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-            final Icon appIcon = appInfo.icon != 0
-                    ? Icon.createWithResource(packageName, appInfo.icon)
-                    : Icon.createWithResource(Resources.getSystem(),
-                            com.android.internal.R.drawable.sym_def_app_icon);
 
+            final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
             builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
                     .setContentText(message)
-                    .setContentIntent(PendingIntent.getActivity(mContext, packageName.hashCode(),
-                            settingsIntent, FLAG_CANCEL_CURRENT))
+                    .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
+                            settingsIntent, FLAG_CANCEL_CURRENT, null, user))
                     .setStyle(new Notification.BigTextStyle().bigText(message))
-                    .setLargeIcon(appIcon);
+                    .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
             return true;
         }
         return false;
@@ -191,4 +215,17 @@
     private void unregisterAppOpsListener() {
         mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
     }
+
+    /**
+     * Bakes a drawable into a bitmap.
+     */
+    private Bitmap createBitmap(Drawable d) {
+        Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+                Config.ARGB_8888);
+        Canvas c = new Canvas(bitmap);
+        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+        d.draw(c);
+        c.setBitmap(null);
+        return bitmap;
+    }
 }
diff --git a/com/android/systemui/pip/phone/PipUtils.java b/com/android/systemui/pip/phone/PipUtils.java
index a8cdd1b..2f53de9 100644
--- a/com/android/systemui/pip/phone/PipUtils.java
+++ b/com/android/systemui/pip/phone/PipUtils.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
@@ -24,33 +25,35 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
 
 public class PipUtils {
 
     private static final String TAG = "PipUtils";
 
     /**
-     * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if
-     *         none exists.
+     * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
+     *         The component name may be null if no such activity exists.
      */
-    public static ComponentName getTopPinnedActivity(Context context,
+    public static Pair<ComponentName, Integer> getTopPinnedActivity(Context context,
             IActivityManager activityManager) {
         try {
             final String sysUiPackageName = context.getPackageName();
-            final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+            final StackInfo pinnedStackInfo =
+                    activityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
                 for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
                     ComponentName cn = ComponentName.unflattenFromString(
                             pinnedStackInfo.taskNames[i]);
                     if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
-                        return cn;
+                        return new Pair<>(cn, pinnedStackInfo.taskUserIds[i]);
                     }
                 }
             }
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to get pinned stack.");
         }
-        return null;
+        return new Pair<>(null, 0);
     }
 }
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index e8c1295..e0445c1 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -53,7 +53,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.PINNED_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_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 /**
@@ -121,6 +123,7 @@
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
     private boolean mInitialized;
     private int mPipTaskId = TASK_ID_NO_PIP;
+    private int mPinnedStackId = INVALID_STACK_ID;
     private ComponentName mPipComponentName;
     private MediaController mPipMediaController;
     private String[] mLastPackagesResourceGranted;
@@ -336,9 +339,11 @@
         mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
         if (removePipStack) {
             try {
-                mActivityManager.removeStack(PINNED_STACK_ID);
+                mActivityManager.removeStack(mPinnedStackId);
             } catch (RemoteException e) {
                 Log.e(TAG, "removeStack failed", e);
+            } finally {
+                mPinnedStackId = INVALID_STACK_ID;
             }
         }
         for (int i = mListeners.size() - 1; i >= 0; --i) {
@@ -424,7 +429,7 @@
         }
         try {
             int animationDurationMs = -1;
-            mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+            mActivityManager.resizeStack(mPinnedStackId, mCurrentPipBounds,
                     true, true, true, animationDurationMs);
         } catch (RemoteException e) {
             Log.e(TAG, "resizeStack failed", e);
@@ -502,7 +507,8 @@
     private StackInfo getPinnedStackInfo() {
         StackInfo stackInfo = null;
         try {
-            stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            stackInfo = mActivityManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
         } catch (RemoteException e) {
             Log.e(TAG, "getStackInfo failed", e);
         }
@@ -654,7 +660,7 @@
         }
 
         @Override
-        public void onActivityPinned(String packageName, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
             if (!checkCurrentUserId(mContext, DEBUG)) {
                 return;
@@ -665,6 +671,7 @@
                 return;
             }
             if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+            mPinnedStackId = stackInfo.stackId;
             mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
             mPipComponentName = ComponentName.unflattenFromString(
                     stackInfo.taskNames[stackInfo.taskNames.length - 1]);
diff --git a/com/android/systemui/qs/AlphaControlledSignalTileView.java b/com/android/systemui/qs/AlphaControlledSignalTileView.java
new file mode 100644
index 0000000..2c7ec70
--- /dev/null
+++ b/com/android/systemui/qs/AlphaControlledSignalTileView.java
@@ -0,0 +1,86 @@
+/*
+ * 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.qs;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import com.android.systemui.qs.tileimpl.SlashImageView;
+
+
+/**
+ * Creates AlphaControlledSlashImageView instead of SlashImageView
+ */
+public class AlphaControlledSignalTileView extends SignalTileView {
+    public AlphaControlledSignalTileView(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected SlashImageView createSlashImageView(Context context) {
+        return new AlphaControlledSlashImageView(context);
+    }
+
+    /**
+     * Creates AlphaControlledSlashDrawable instead of regular SlashDrawables
+     */
+    public static class AlphaControlledSlashImageView extends SlashImageView {
+        public AlphaControlledSlashImageView(Context context) {
+            super(context);
+        }
+
+        public void setFinalImageTintList(ColorStateList tint) {
+            super.setImageTintList(tint);
+            final SlashDrawable slash = getSlash();
+            if (slash != null) {
+                ((AlphaControlledSlashDrawable)slash).setFinalTintList(tint);
+            }
+        }
+
+        @Override
+        protected void ensureSlashDrawable() {
+            if (getSlash() == null) {
+                final SlashDrawable slash = new AlphaControlledSlashDrawable(getDrawable());
+                setSlash(slash);
+                slash.setAnimationEnabled(getAnimationEnabled());
+                setImageViewDrawable(slash);
+            }
+        }
+    }
+
+    /**
+     * SlashDrawable that disobeys orders to change its drawable's tint except when you tell
+     * it not to disobey. The slash still will animate its alpha.
+     */
+    public static class AlphaControlledSlashDrawable extends SlashDrawable {
+        AlphaControlledSlashDrawable(Drawable d) {
+            super(d);
+        }
+
+        @Override
+        protected void setDrawableTintList(ColorStateList tint) {
+        }
+
+        /**
+         * Set a target tint list instead of
+         */
+        public void setFinalTintList(ColorStateList tint) {
+            super.setDrawableTintList(tint);
+        }
+    }
+}
+
diff --git a/com/android/systemui/qs/SignalTileView.java b/com/android/systemui/qs/SignalTileView.java
index b300e4a..9ee40cc 100644
--- a/com/android/systemui/qs/SignalTileView.java
+++ b/com/android/systemui/qs/SignalTileView.java
@@ -63,13 +63,17 @@
     @Override
     protected View createIcon() {
         mIconFrame = new FrameLayout(mContext);
-        mSignal = new SlashImageView(mContext);
+        mSignal = createSlashImageView(mContext);
         mIconFrame.addView(mSignal);
         mOverlay = new ImageView(mContext);
         mIconFrame.addView(mOverlay, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
         return mIconFrame;
     }
 
+    protected SlashImageView createSlashImageView(Context context) {
+        return new SlashImageView(context);
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/com/android/systemui/qs/SlashDrawable.java b/com/android/systemui/qs/SlashDrawable.java
index c356148..a9b2376 100644
--- a/com/android/systemui/qs/SlashDrawable.java
+++ b/com/android/systemui/qs/SlashDrawable.java
@@ -197,11 +197,15 @@
     public void setTintList(@Nullable ColorStateList tint) {
         mTintList = tint;
         super.setTintList(tint);
-        mDrawable.setTintList(tint);
+        setDrawableTintList(tint);
         mPaint.setColor(tint.getDefaultColor());
         invalidateSelf();
     }
 
+    protected void setDrawableTintList(@Nullable ColorStateList tint) {
+        mDrawable.setTintList(tint);
+    }
+
     @Override
     public void setTintMode(@NonNull Mode tintMode) {
         mTintMode = tintMode;
diff --git a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 8074cb9..e8c8b90 100644
--- a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
 
+import com.android.systemui.qs.AlphaControlledSignalTileView.AlphaControlledSlashImageView;
 import java.util.Objects;
 
 public class QSIconViewImpl extends QSIconView {
@@ -138,7 +139,12 @@
                 animateGrayScale(mTint, color, iv);
                 mTint = color;
             } else {
-                setTint(iv, color);
+                if (iv instanceof AlphaControlledSlashImageView) {
+                    ((AlphaControlledSlashImageView)iv)
+                            .setFinalImageTintList(ColorStateList.valueOf(color));
+                } else {
+                    setTint(iv, color);
+                }
                 mTint = color;
             }
         }
@@ -149,6 +155,10 @@
     }
 
     public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
+        if (iv instanceof AlphaControlledSlashImageView) {
+            ((AlphaControlledSlashImageView)iv)
+                    .setFinalImageTintList(ColorStateList.valueOf(toColor));
+        }
         if (ValueAnimator.areAnimatorsEnabled()) {
             final float fromAlpha = Color.alpha(fromColor);
             final float toAlpha = Color.alpha(toColor);
diff --git a/com/android/systemui/qs/tileimpl/SlashImageView.java b/com/android/systemui/qs/tileimpl/SlashImageView.java
index 97e9c3d..63d6f82 100644
--- a/com/android/systemui/qs/tileimpl/SlashImageView.java
+++ b/com/android/systemui/qs/tileimpl/SlashImageView.java
@@ -34,7 +34,15 @@
         super(context);
     }
 
-    private void ensureSlashDrawable() {
+    protected SlashDrawable getSlash() {
+        return mSlash;
+    }
+
+    protected void setSlash(SlashDrawable slash) {
+        mSlash = slash;
+    }
+
+    protected void ensureSlashDrawable() {
         if (mSlash == null) {
             mSlash = new SlashDrawable(getDrawable());
             mSlash.setAnimationEnabled(mAnimationEnabled);
@@ -56,10 +64,18 @@
         }
     }
 
+    protected void setImageViewDrawable(SlashDrawable slash) {
+        super.setImageDrawable(slash);
+    }
+
     public void setAnimationEnabled(boolean enabled) {
         mAnimationEnabled = enabled;
     }
 
+    public boolean getAnimationEnabled() {
+        return mAnimationEnabled;
+    }
+
     private void setSlashState(@NonNull SlashState slashState) {
         ensureSlashDrawable();
         mSlash.setRotation(slashState.rotation);
diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java
index 8d62f2a..81b8622 100644
--- a/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -23,6 +23,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
@@ -34,10 +35,8 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.R.drawable;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -135,11 +134,7 @@
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        BluetoothDeviceLayerDrawable drawable = createLayerDrawable(mContext,
-                                R.drawable.ic_qs_bluetooth_connected, batteryLevel,
-                                mContext.getResources().getFraction(
-                                        R.fraction.bt_battery_scale_fraction, 1, 1));
-                        state.icon = new DrawableIcon(drawable);
+                        state.icon = new BluetoothBatteryDrawable(batteryLevel);
                     }
                 }
                 state.contentDescription = mContext.getString(
@@ -215,6 +210,22 @@
         return new BluetoothDetailAdapter();
     }
 
+    private class BluetoothBatteryDrawable extends Icon {
+        private int mLevel;
+
+        BluetoothBatteryDrawable(int level) {
+            mLevel = level;
+        }
+
+        @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));
+        }
+    }
+
     protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
         // We probably won't ever have space in the UI for more than 20 devices, so don't
         // get info for them.
diff --git a/com/android/systemui/qs/tiles/CellularTile.java b/com/android/systemui/qs/tiles/CellularTile.java
index 2e389ba..0ce3e6a 100644
--- a/com/android/systemui/qs/tiles/CellularTile.java
+++ b/com/android/systemui/qs/tiles/CellularTile.java
@@ -266,7 +266,7 @@
         }
 
         @Override
-        public void setNoSims(boolean show) {
+        public void setNoSims(boolean show, boolean simDetected) {
             mInfo.noSim = show;
             if (mInfo.noSim) {
                 // Make sure signal gets cleared out when no sims.
diff --git a/com/android/systemui/qs/tiles/WifiTile.java b/com/android/systemui/qs/tiles/WifiTile.java
index 33b1512..2370273 100644
--- a/com/android/systemui/qs/tiles/WifiTile.java
+++ b/com/android/systemui/qs/tiles/WifiTile.java
@@ -37,10 +37,10 @@
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
+import com.android.systemui.qs.AlphaControlledSignalTileView;
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
@@ -104,7 +104,7 @@
 
     @Override
     public QSIconView createTileView(Context context) {
-        return new SignalTileView(context);
+        return new AlphaControlledSignalTileView(context);
     }
 
     @Override
diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java
index 406bcac..283ac0c 100644
--- a/com/android/systemui/recents/Recents.java
+++ b/com/android/systemui/recents/Recents.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.recents;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.app.ActivityManager;
@@ -437,9 +440,12 @@
         int currentUser = sSystemServicesProxy.getCurrentUser();
         SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        final int activityType = runningTask != null
+                ? runningTask.configuration.windowConfiguration.getActivityType()
+                : ACTIVITY_TYPE_UNDEFINED;
         boolean screenPinningActive = ssp.isScreenPinningActive();
-        boolean isRunningTaskInHomeOrRecentsStack = runningTask != null &&
-                ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
+        boolean isRunningTaskInHomeOrRecentsStack =
+                activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
         if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
             logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
             if (runningTask.supportsSplitScreenMultiWindow) {
diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java
index f545556..86b7790 100644
--- a/com/android/systemui/recents/RecentsActivity.java
+++ b/com/android/systemui/recents/RecentsActivity.java
@@ -358,6 +358,9 @@
         mScrimViews = new SystemBarScrimViews(this);
         getWindow().getAttributes().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+        if (Recents.getConfiguration().isLowRamDevice) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
 
         mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
         mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index aecf95f..3e2a5f3 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.recents;
 
-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.isHomeOrRecentsStack;
+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;
@@ -173,7 +173,7 @@
         }
 
         @Override
-        public void onActivityPinned(String packageName, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             // Check this is for the right user
             if (!checkCurrentUserId(mContext, false /* debug */)) {
                 return;
@@ -533,7 +533,9 @@
         if (runningTask == null) return;
 
         // Find the task in the recents list
-        boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
+        boolean isRunningTaskInHomeStack =
+                runningTask.configuration.windowConfiguration.getActivityType()
+                        == ACTIVITY_TYPE_HOME;
         ArrayList<Task> tasks = focusedStack.getStackTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
@@ -565,8 +567,7 @@
 
         // Launch the task
         ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
-                null /* resultListener */);
+                mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
     }
 
     /**
@@ -584,9 +585,10 @@
 
         // Return early if there is no running task (can't determine affiliated tasks in this case)
         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
         if (runningTask == null) return;
         // Return early if the running task is in the home/recents stack (optimization)
-        if (isHomeOrRecentsStack(runningTask.stackId)) return;
+        if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
 
         // Find the task in the recents list
         ArrayList<Task> tasks = focusedStack.getStackTasks();
@@ -639,8 +641,7 @@
 
         // Launch the task
         ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
-                null /* resultListener */);
+                mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
     }
 
     public void showNextAffiliatedTask() {
@@ -872,7 +873,9 @@
             getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
                     Rect windowOverrideRect) {
         final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
-        if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
+        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();
diff --git a/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java b/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
index e02fb14..e4a4f59 100644
--- a/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
+++ b/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 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.
@@ -22,5 +22,15 @@
  * This is sent when the stack action button should be hidden.
  */
 public class HideStackActionButtonEvent extends EventBus.Event {
-    // Simple event
+
+    // Whether or not to translate the stack action button when hiding it
+    public final boolean translate;
+
+    public HideStackActionButtonEvent() {
+        this(true);
+    }
+
+    public HideStackActionButtonEvent(boolean translate) {
+        this.translate = translate;
+    }
 }
diff --git a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 3db106e..862a1ee 100644
--- a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.recents.events.activity;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
 import android.graphics.Rect;
 
 import com.android.systemui.recents.events.EventBus;
@@ -30,15 +33,23 @@
     public final TaskView taskView;
     public final Task task;
     public final Rect targetTaskBounds;
-    public final int targetTaskStack;
+    public final int targetWindowingMode;
+    public final int targetActivityType;
     public final boolean screenPinningRequested;
 
-    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, int targetTaskStack,
+    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
             boolean screenPinningRequested) {
+        this(taskView, task, targetTaskBounds, screenPinningRequested,
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
+            boolean screenPinningRequested, int windowingMode, int activityType) {
         this.taskView = taskView;
         this.task = task;
         this.targetTaskBounds = targetTaskBounds;
-        this.targetTaskStack = targetTaskStack;
+        this.targetWindowingMode = windowingMode;
+        this.targetActivityType = activityType;
         this.screenPinningRequested = screenPinningRequested;
     }
 
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index 7177782..bddf9a5 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,13 +16,15 @@
 
 package com.android.systemui.recents.misc;
 
-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.HOME_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.RECENTS_STACK_ID;
+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_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.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 
 import android.annotation.NonNull;
@@ -34,6 +36,7 @@
 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;
@@ -172,7 +175,7 @@
         public void onTaskStackChangedBackground() { }
         public void onTaskStackChanged() { }
         public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
-        public void onActivityPinned(String packageName, int taskId) { }
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
         public void onActivityUnpinned() { }
         public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
         public void onPinnedStackAnimationStarted() { }
@@ -227,9 +230,11 @@
         }
 
         @Override
-        public void onActivityPinned(String packageName, int taskId) throws RemoteException {
+        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, taskId, 0, packageName).sendToTarget();
+            mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
+                    new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
         }
 
         @Override
@@ -481,16 +486,22 @@
     public ActivityManager.RunningTaskInfo getRunningTask() {
         // Note: The set of running tasks from the system is ordered by recency
         List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
-        if (tasks != null && !tasks.isEmpty()) {
-            // Find the first task in a valid stack, we ignore everything from the Recents and PiP
-            // stacks
-            for (int i = 0; i < tasks.size(); i++) {
-                ActivityManager.RunningTaskInfo task = tasks.get(i);
-                int stackId = task.stackId;
-                if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) {
-                    return task;
-                }
+        if (tasks == null || tasks.isEmpty()) {
+            return null;
+        }
+
+        // Find the first task in a valid stack, we ignore everything from the Recents and PiP
+        // stacks
+        for (int i = 0; i < tasks.size(); i++) {
+            final ActivityManager.RunningTaskInfo task = tasks.get(i);
+            final WindowConfiguration winConfig = task.configuration.windowConfiguration;
+            if (winConfig.getActivityType() == ACTIVITY_TYPE_RECENTS) {
+                continue;
             }
+            if (winConfig.getWindowingMode() == WINDOWING_MODE_PINNED) {
+                continue;
+            }
+            return task;
         }
         return null;
     }
@@ -517,12 +528,17 @@
             ActivityManager.StackInfo fullscreenStackInfo = null;
             ActivityManager.StackInfo recentsStackInfo = null;
             for (int i = 0; i < stackInfos.size(); i++) {
-                StackInfo stackInfo = stackInfos.get(i);
-                if (stackInfo.stackId == HOME_STACK_ID) {
+                final StackInfo stackInfo = stackInfos.get(i);
+                final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
+                final int activityType = winConfig.getActivityType();
+                final int windowingMode = winConfig.getWindowingMode();
+                if (activityType == ACTIVITY_TYPE_HOME) {
                     homeStackInfo = stackInfo;
-                } else if (stackInfo.stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                } else if (activityType == ACTIVITY_TYPE_STANDARD
+                        && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                            || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
                     fullscreenStackInfo = stackInfo;
-                } else if (stackInfo.stackId == RECENTS_STACK_ID) {
+                } else if (activityType == ACTIVITY_TYPE_RECENTS) {
                     recentsStackInfo = stackInfo;
                 }
             }
@@ -576,7 +592,7 @@
         try {
             final ActivityOptions options = ActivityOptions.makeBasic();
             options.setDockCreateMode(createMode);
-            options.setLaunchStackId(DOCKED_STACK_ID);
+            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
             mIam.startActivityFromRecents(taskId, options.toBundle());
             return true;
         } catch (Exception e) {
@@ -601,34 +617,6 @@
     }
 
     /**
-     * Returns whether the given stack id is the home stack id.
-     */
-    public static boolean isHomeStack(int stackId) {
-        return stackId == HOME_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the pinned stack id.
-     */
-    public static boolean isPinnedStack(int stackId){
-        return stackId == PINNED_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the docked stack id.
-     */
-    public static boolean isDockedStack(int stackId) {
-        return stackId == DOCKED_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the freeform workspace stack id.
-     */
-    public static boolean isFreeformStack(int stackId) {
-        return stackId == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
-    /**
      * @return whether there are any docked tasks for the current user.
      */
     public boolean hasDockedTask() {
@@ -636,7 +624,8 @@
 
         ActivityManager.StackInfo stackInfo = null;
         try {
-            stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
+            stackInfo =
+                    mIam.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -737,14 +726,12 @@
         }
     }
 
-    /**
-     * Moves a task into another stack.
-     */
-    public void moveTaskToStack(int taskId, int stackId) {
+    /** Set the task's windowing mode. */
+    public void setTaskWindowingMode(int taskId, int windowingMode) {
         if (mIam == null) return;
 
         try {
-            mIam.positionTaskInStack(taskId, stackId, 0);
+            mIam.setTaskWindowingMode(taskId, windowingMode, false /* onTop */);
         } catch (RemoteException | IllegalArgumentException e) {
             e.printStackTrace();
         }
@@ -1101,9 +1088,10 @@
 
         try {
             // Use the recents stack bounds, fallback to fullscreen stack if it is null
-            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID);
+            ActivityManager.StackInfo stackInfo =
+                    mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
             if (stackInfo == null) {
-                stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
+                stackInfo = mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
             }
             if (stackInfo != null) {
                 windowRect.set(stackInfo.bounds);
@@ -1120,25 +1108,34 @@
                 opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
     }
 
+    public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
+            ActivityOptions options,
+            @Nullable final StartActivityFromRecentsResultListener resultListener) {
+        startActivityFromRecents(context, taskKey, taskName, options,
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, resultListener);
+    }
+
     /** Starts an activity from recents. */
     public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
-            ActivityOptions options, int stackId,
+            ActivityOptions options, int windowingMode, int activityType,
             @Nullable final StartActivityFromRecentsResultListener resultListener) {
         if (mIam == null) {
             return;
         }
-        if (taskKey.stackId == DOCKED_STACK_ID) {
+        if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // We show non-visible docked tasks in Recents, but we always want to launch
             // them in the fullscreen stack.
             if (options == null) {
                 options = ActivityOptions.makeBasic();
             }
-            options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID);
-        } else if (stackId != INVALID_STACK_ID) {
+            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        } else if (windowingMode != WINDOWING_MODE_UNDEFINED
+                || activityType != ACTIVITY_TYPE_UNDEFINED) {
             if (options == null) {
                 options = ActivityOptions.makeBasic();
             }
-            options.setLaunchStackId(stackId);
+            options.setLaunchWindowingMode(windowingMode);
+            options.setLaunchActivityType(activityType);
         }
         final ActivityOptions finalOptions = options;
 
@@ -1307,6 +1304,20 @@
         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;
@@ -1342,8 +1353,10 @@
                         break;
                     }
                     case ON_ACTIVITY_PINNED: {
+                        final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1);
+                            mTaskStackListeners.get(i).onActivityPinned(
+                                    info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
                         }
                         break;
                     }
diff --git a/com/android/systemui/recents/model/HighResThumbnailLoader.java b/com/android/systemui/recents/model/HighResThumbnailLoader.java
index 48fa6c3..6414ea1 100644
--- a/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ b/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -187,7 +187,7 @@
     }
 
     @Override
-    public void onTaskStackIdChanged() {
+    public void onTaskWindowingModeChanged() {
     }
 
     private final Runnable mLoader = new Runnable() {
diff --git a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 8d31730..d5e0313 100644
--- a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -16,6 +16,8 @@
 
 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;
@@ -155,12 +157,13 @@
             ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
 
             // Compose the task key
-            Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
+            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 = SystemServicesProxy.isFreeformStack(t.stackId);
+            boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
             boolean isStackTask;
             if (Recents.getConfiguration().isGridEnabled) {
                 // When grid layout is enabled, we only show the first
diff --git a/com/android/systemui/recents/model/Task.java b/com/android/systemui/recents/model/Task.java
index 9e6bf85..abdb5cb 100644
--- a/com/android/systemui/recents/model/Task.java
+++ b/com/android/systemui/recents/model/Task.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.recents.model;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -46,8 +48,8 @@
         public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
         /* Notifies when a task has been unbound */
         public void onTaskDataUnloaded();
-        /* Notifies when a task's stack id has changed. */
-        public void onTaskStackIdChanged();
+        /* Notifies when a task's windowing mode has changed. */
+        public void onTaskWindowingModeChanged();
     }
 
     /* The Task Key represents the unique primary key for the task */
@@ -55,7 +57,7 @@
         @ViewDebug.ExportedProperty(category="recents")
         public final int id;
         @ViewDebug.ExportedProperty(category="recents")
-        public int stackId;
+        public int windowingMode;
         @ViewDebug.ExportedProperty(category="recents")
         public final Intent baseIntent;
         @ViewDebug.ExportedProperty(category="recents")
@@ -67,10 +69,10 @@
 
         private int mHashCode;
 
-        public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
+        public TaskKey(int id, int windowingMode, Intent intent, int userId, long firstActiveTime,
                 long lastActiveTime) {
             this.id = id;
-            this.stackId = stackId;
+            this.windowingMode = windowingMode;
             this.baseIntent = intent;
             this.userId = userId;
             this.firstActiveTime = firstActiveTime;
@@ -78,8 +80,8 @@
             updateHashCode();
         }
 
-        public void setStackId(int stackId) {
-            this.stackId = stackId;
+        public void setWindowingMode(int windowingMode) {
+            this.windowingMode = windowingMode;
             updateHashCode();
         }
 
@@ -93,7 +95,9 @@
                 return false;
             }
             TaskKey otherKey = (TaskKey) o;
-            return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
+            return id == otherKey.id
+                    && windowingMode == otherKey.windowingMode
+                    && userId == otherKey.userId;
         }
 
         @Override
@@ -103,12 +107,12 @@
 
         @Override
         public String toString() {
-            return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
-                    lastActiveTime;
+            return "id=" + id + " windowingMode=" + windowingMode + " user=" + userId
+                    + " lastActiveTime=" + lastActiveTime;
         }
 
         private void updateHashCode() {
-            mHashCode = Objects.hash(id, stackId, userId);
+            mHashCode = Objects.hash(id, windowingMode, userId);
         }
     }
 
@@ -277,14 +281,12 @@
         this.group = group;
     }
 
-    /**
-     * Updates the stack id of this task.
-     */
-    public void setStackId(int stackId) {
-        key.setStackId(stackId);
+    /** Updates the task's windowing mode. */
+    public void setWindowingMode(int windowingMode) {
+        key.setWindowingMode(windowingMode);
         int callbackCount = mCallbacks.size();
         for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskStackIdChanged();
+            mCallbacks.get(i).onTaskWindowingModeChanged();
         }
     }
 
@@ -293,7 +295,7 @@
      */
     public boolean isFreeformTask() {
         SystemServicesProxy ssp = Recents.getSystemServices();
-        return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
+        return ssp.hasFreeformWorkspaceSupport() && key.windowingMode == WINDOWING_MODE_FREEFORM;
     }
 
     /** Notifies the callback listeners that this task has been loaded */
diff --git a/com/android/systemui/recents/model/TaskKeyCache.java b/com/android/systemui/recents/model/TaskKeyCache.java
index be99f93..247a654 100644
--- a/com/android/systemui/recents/model/TaskKeyCache.java
+++ b/com/android/systemui/recents/model/TaskKeyCache.java
@@ -45,7 +45,7 @@
     final V getAndInvalidateIfModified(Task.TaskKey key) {
         Task.TaskKey lastKey = mKeys.get(key.id);
         if (lastKey != null) {
-            if ((lastKey.stackId != key.stackId) ||
+            if ((lastKey.windowingMode != key.windowingMode) ||
                     (lastKey.lastActiveTime != key.lastActiveTime)) {
                 // The task has updated (been made active since the last time it was put into the
                 // LRU cache) or the stack id for the task has changed, invalidate that cache item
diff --git a/com/android/systemui/recents/model/TaskStack.java b/com/android/systemui/recents/model/TaskStack.java
index 6e3be09..fdae917 100644
--- a/com/android/systemui/recents/model/TaskStack.java
+++ b/com/android/systemui/recents/model/TaskStack.java
@@ -18,8 +18,8 @@
 
 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.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+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;
@@ -115,7 +115,7 @@
     /**
      * Moves the given task.
      */
-    public void moveTaskToStack(Task task, int insertIndex, int newStackId) {
+    public void setTaskWindowingMode(Task task, int insertIndex, int windowingMode) {
         int taskIndex = indexOf(task);
         if (taskIndex != insertIndex) {
             mTasks.remove(taskIndex);
@@ -127,7 +127,7 @@
 
         // Update the stack id now, after we've moved the task, and before we update the
         // filtered tasks
-        task.setStackId(newStackId);
+        task.setWindowingMode(windowingMode);
         updateFilteredTasks();
     }
 
@@ -590,17 +590,15 @@
         mCb = cb;
     }
 
-    /**
-     * Moves the given task to either the front of the freeform workspace or the stack.
-     */
-    public void moveTaskToStack(Task task, int newStackId) {
+    /** 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() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
+        if (!task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FREEFORM)) {
             // Insert freeform tasks at the front
-            mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
-        } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
+            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--) {
@@ -609,7 +607,7 @@
                     break;
                 }
             }
-            mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
+            mStackTaskList.setTaskWindowingMode(task, insertIndex, windowingMode);
         }
     }
 
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
index b2675d7..ee05d81 100644
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -16,14 +16,17 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-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.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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
@@ -107,7 +110,7 @@
      */
     public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
             final TaskStackView stackView, final TaskView taskView,
-            final boolean screenPinningRequested, final int destinationStack) {
+            final boolean screenPinningRequested, final int windowingMode, final int activityType) {
 
         final ActivityOptions.OnAnimationStartedListener animStartedListener;
         final AppTransitionAnimationSpecsFuture transitionFuture;
@@ -116,8 +119,8 @@
             // Fetch window rect here already in order not to be blocked on lock contention in WM
             // when the future calls it.
             final Rect windowRect = Recents.getSystemServices().getWindowRect();
-            transitionFuture = getAppTransitionFuture(
-                    () -> composeAnimationSpecs(task, stackView, destinationStack, windowRect));
+            transitionFuture = getAppTransitionFuture(() -> composeAnimationSpecs(
+                    task, stackView, windowingMode, activityType, windowRect));
             animStartedListener = new OnAnimationStartedListener() {
                 private boolean mHandled;
 
@@ -180,7 +183,8 @@
         if (taskView == null) {
             // If there is no task view, then we do not need to worry about animating out occluding
             // task views, and we can launch immediately
-            startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
+            startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                    windowingMode, activityType);
         } else {
             LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
                     screenPinningRequested);
@@ -189,13 +193,14 @@
                     @Override
                     public void run() {
                         startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                                destinationStack);
+                                windowingMode, activityType);
                     }
                 });
                 EventBus.getDefault().send(launchStartedEvent);
             } else {
                 EventBus.getDefault().send(launchStartedEvent);
-                startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
+                startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                        windowingMode, activityType);
             }
         }
         Recents.getSystemServices().sendCloseSystemWindows(
@@ -224,13 +229,13 @@
      *
      * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
      *                 we are toggling recents and the launch-to task is now offscreen.
-     * @param destinationStack id of the stack to put the task into.
      */
     private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
             ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
-            int destinationStack) {
+            int windowingMode, int activityType) {
         SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, destinationStack,
+        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
+                activityType,
                 succeeded -> {
             if (succeeded) {
                 // Keep track of the index of the task launch
@@ -310,11 +315,9 @@
      * Composes the animation specs for all the tasks in the target stack.
      */
     private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
-            final TaskStackView stackView, final int destinationStack, Rect windowRect) {
-        // Ensure we have a valid target stack id
-        final int targetStackId = destinationStack != INVALID_STACK_ID ?
-                destinationStack : task.key.stackId;
-        if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
+            final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+        if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME
+                || windowingMode == WINDOWING_MODE_PINNED) {
             return null;
         }
 
@@ -329,9 +332,12 @@
         List<AppTransitionAnimationSpec> specs = new ArrayList<>();
 
         // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
-        // check for INVALID_STACK_ID
-        if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID || targetStackId == DOCKED_STACK_ID
-                || targetStackId == ASSISTANT_STACK_ID || targetStackId == INVALID_STACK_ID) {
+        // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                || activityType == ACTIVITY_TYPE_ASSISTANT
+                || windowingMode == WINDOWING_MODE_UNDEFINED) {
             if (taskView == null) {
                 specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
             } else {
@@ -353,7 +359,7 @@
         int taskCount = tasks.size();
         for (int i = taskCount - 1; i >= 0; i--) {
             Task t = tasks.get(i);
-            if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
+            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
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index c44cd72..c7edb9a 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -174,8 +174,6 @@
                         ? R.layout.recents_low_ram_stack_action_button
                         : R.layout.recents_stack_action_button,
                     this, false);
-            mStackActionButton.setOnClickListener(
-                    v -> EventBus.getDefault().send(new DismissAllTaskViewsEvent()));
 
             mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
             mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
@@ -205,10 +203,6 @@
                         mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
                         mStackButtonShadowColor);
             }
-            if (Recents.getConfiguration().isLowRamDevice) {
-                int bgColor = Utils.getColorAttr(mContext, R.attr.clearAllBackgroundColor);
-                mStackActionButton.setBackgroundColor(bgColor);
-            }
         }
 
         // Let's also require dark status and nav bars if the text is dark
@@ -338,8 +332,7 @@
             Task task = mTaskStackView.getFocusedTask();
             if (task != null) {
                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
-                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
-                        INVALID_STACK_ID, false));
+                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
 
                 if (logEvent != 0) {
                     MetricsLogger.action(getContext(), logEvent,
@@ -363,32 +356,13 @@
             Task task = getStack().getLaunchTarget();
             if (task != null) {
                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
-                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
-                        INVALID_STACK_ID, false));
+                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
                 return true;
             }
         }
         return false;
     }
 
-    /** Launches a given task. */
-    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
-        if (mTaskStackView != null) {
-            // Iterate the stack views and try and find the given task.
-            List<TaskView> taskViews = mTaskStackView.getTaskViews();
-            int taskViewCount = taskViews.size();
-            for (int j = 0; j < taskViewCount; j++) {
-                TaskView tv = taskViews.get(j);
-                if (tv.getTask() == task) {
-                    EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
-                            destinationStack, false));
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * Hides the task stack and shows the empty view.
      */
@@ -570,9 +544,10 @@
     public final void onBusEvent(LaunchTaskEvent event) {
         mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
         mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
-                event.taskView, event.screenPinningRequested, event.targetTaskStack);
+                event.taskView, event.screenPinningRequested, event.targetWindowingMode,
+                event.targetActivityType);
         if (Recents.getConfiguration().isLowRamDevice) {
-            hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, false /* translate */);
+            EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
         }
     }
 
@@ -580,7 +555,7 @@
         int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
         if (RecentsDebugFlags.Static.EnableStackActionButton) {
             // Hide the stack action button
-            hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
+            EventBus.getDefault().send(new HideStackActionButtonEvent());
         }
         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
 
@@ -741,13 +716,10 @@
             animateBackgroundScrim(getOpaqueScrimAlpha(),
                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
         }
-        if (Recents.getConfiguration().isLowRamDevice && mEmptyView.getVisibility() != View.VISIBLE) {
-            showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, false /* translate */);
-        }
     }
 
     public final void onBusEvent(AllTaskViewsDismissedEvent event) {
-        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
+        EventBus.getDefault().send(new HideStackActionButtonEvent());
     }
 
     public final void onBusEvent(DismissAllTaskViewsEvent event) {
@@ -795,7 +767,8 @@
             mStackActionButton.setVisibility(View.VISIBLE);
             mStackActionButton.setAlpha(0f);
             if (translate) {
-                mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() *
+                        (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
             } else {
                 mStackActionButton.setTranslationY(0f);
             }
@@ -841,8 +814,8 @@
 
         if (mStackActionButton.getVisibility() == View.VISIBLE) {
             if (translate) {
-                mStackActionButton.animate()
-                    .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
+                        * (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
             }
             mStackActionButton.animate()
                     .alpha(0f)
@@ -954,7 +927,7 @@
     /**
      * @return the bounds of the stack action button.
      */
-    private Rect getStackActionButtonBoundsFromStackLayout() {
+    Rect getStackActionButtonBoundsFromStackLayout() {
         Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
         int left, top;
         if (Recents.getConfiguration().isLowRamDevice) {
@@ -976,6 +949,16 @@
         return actionButtonRect;
     }
 
+    View getStackActionButton() {
+        return mStackActionButton;
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+        mTouchHandler.cancelStackActionButtonClick();
+    }
+
     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/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 46619c2..b6b24bc 100644
--- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -23,6 +23,7 @@
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 
@@ -31,6 +32,8 @@
 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;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -99,8 +102,7 @@
 
     /** Touch preprocessing for handling below */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        handleTouchEvent(ev);
-        return mDragRequested;
+        return handleTouchEvent(ev) || mDragRequested;
     }
 
     /** Handles touch events once we have intercepted them */
@@ -183,22 +185,47 @@
         }
     }
 
+    void cancelStackActionButtonClick() {
+        mRv.getStackActionButton().setPressed(false);
+    }
+
+    private boolean isWithinStackActionButton(float x, float y) {
+        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+        return mRv.getStackActionButton().getVisibility() == View.VISIBLE &&
+                mRv.getStackActionButton().pointInView(x - rect.left, y - rect.top, 0 /* slop */);
+    }
+
+    private void changeStackActionButtonDrawableHotspot(float x, float y) {
+        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+        mRv.getStackActionButton().drawableHotspotChanged(x - rect.left, y - rect.top);
+    }
+
     /**
      * Handles dragging touch events
      */
-    private void handleTouchEvent(MotionEvent ev) {
+    private boolean handleTouchEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
+        boolean consumed = false;
+        float evX = ev.getX();
+        float evY = ev.getY();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                mDownPos.set((int) ev.getX(), (int) ev.getY());
+                mDownPos.set((int) evX, (int) evY);
                 mDeviceId = ev.getDeviceId();
+
+                if (isWithinStackActionButton(evX, evY)) {
+                    changeStackActionButtonDrawableHotspot(evX, evY);
+                    mRv.getStackActionButton().setPressed(true);
+                }
                 break;
             case MotionEvent.ACTION_MOVE: {
-                float evX = ev.getX();
-                float evY = ev.getY();
                 float x = evX - mTaskViewOffset.x;
                 float y = evY - mTaskViewOffset.y;
 
+                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+                    changeStackActionButtonDrawableHotspot(evX, evY);
+                }
+
                 if (mDragRequested) {
                     if (!mIsDragging) {
                         mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
@@ -232,9 +259,7 @@
                             EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
                                     currentDropTarget));
                         }
-
                     }
-
                     mTaskView.setTranslationX(x);
                     mTaskView.setTranslationY(y);
                 }
@@ -242,6 +267,11 @@
             }
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL: {
+                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+                    EventBus.getDefault().send(new DismissAllTaskViewsEvent());
+                    consumed = true;
+                }
+                cancelStackActionButtonClick();
                 if (mDragRequested) {
                     boolean cancelled = action == MotionEvent.ACTION_CANCEL;
                     if (cancelled) {
@@ -254,5 +284,6 @@
                 mDeviceId = -1;
             }
         }
+        return consumed;
     }
 }
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 8899e30..3160ee0 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -16,9 +16,8 @@
 
 package com.android.systemui.recents.views;
 
-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.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -1487,7 +1486,7 @@
             Task frontTask = tasks.get(tasks.size() - 1);
             if (frontTask != null && frontTask.isFreeformTask()) {
                 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
-                        frontTask, null, INVALID_STACK_ID, false));
+                        frontTask, null, false));
                 return true;
             }
         }
@@ -1768,8 +1767,17 @@
         }
 
         // In grid layout, the stack action button always remains visible.
-        if (mEnterAnimationComplete && !useGridLayout() &&
-                !Recents.getConfiguration().isLowRamDevice) {
+        if (mEnterAnimationComplete && !useGridLayout()) {
+            if (Recents.getConfiguration().isLowRamDevice) {
+                // Show stack button when user drags down to show older tasks on low ram devices
+                if (mStack.getTaskCount() > 0 && !mStackActionButtonVisible
+                        && mTouchHandler.mIsScrolling && curScroll - prevScroll < 0) {
+                    // Going up
+                    EventBus.getDefault().send(
+                            new ShowStackActionButtonEvent(true /* translate */));
+                }
+                return;
+            }
             if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     mStack.getTaskCount() > 0) {
@@ -1956,6 +1964,9 @@
         // Remove the task from the stack
         mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
         EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
+        if (mStack.getTaskCount() > 0 && Recents.getConfiguration().isLowRamDevice) {
+            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
+        }
 
         MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
                 event.task.key.getComponent().toString());
@@ -2082,18 +2093,18 @@
         }
 
         boolean isFreeformTask = event.task.isFreeformTask();
-        boolean hasChangedStacks =
+        boolean hasChangedWindowingMode =
                 (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
                         (isFreeformTask && event.dropTarget == mStackDropTarget);
 
-        if (hasChangedStacks) {
+        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.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
+                mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FREEFORM);
             } else if (event.dropTarget == mStackDropTarget) {
-                mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
+                mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FULLSCREEN);
             }
             updateLayoutAlgorithm(true /* boundScroll */);
 
@@ -2102,7 +2113,7 @@
                 @Override
                 public void run() {
                     SystemServicesProxy ssp = Recents.getSystemServices();
-                    ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
+                    ssp.setTaskWindowingMode(event.task.key.id, event.task.key.windowingMode);
                 }
             });
         }
@@ -2369,12 +2380,12 @@
                         public void run() {
                             EventBus.getDefault().send(new LaunchTaskEvent(
                                     getChildViewForTask(task), task, null,
-                                    INVALID_STACK_ID, false /* screenPinningRequested */));
+                                    false /* screenPinningRequested */));
                         }
                     });
         } else {
-            EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task),
-                    task, null, INVALID_STACK_ID, false /* screenPinningRequested */));
+            EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), task, null,
+                    false /* screenPinningRequested */));
         }
     }
 
diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java
index ceeebd9..9d63964 100644
--- a/com/android/systemui/recents/views/TaskView.java
+++ b/com/android/systemui/recents/views/TaskView.java
@@ -647,7 +647,7 @@
     }
 
     @Override
-    public void onTaskStackIdChanged() {
+    public void onTaskWindowingModeChanged() {
         // Force rebind the header, the thumbnail does not change due to stack changes
         mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
         mHeaderView.onTaskDataLoaded();
@@ -674,8 +674,7 @@
             mActionButtonView.setTranslationZ(0f);
             screenPinningRequested = true;
         }
-        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
-                screenPinningRequested));
+        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, screenPinningRequested));
 
         MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
                 mTask.key.getComponent().toString());
diff --git a/com/android/systemui/recents/views/TaskViewHeader.java b/com/android/systemui/recents/views/TaskViewHeader.java
index ae922fc..198ecae 100644
--- a/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,6 +16,11 @@
 
 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;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.Nullable;
@@ -57,10 +62,6 @@
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 
-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;
-
 /* The task bar view */
 public class TaskViewHeader extends FrameLayout
         implements View.OnClickListener, View.OnLongClickListener {
@@ -172,7 +173,7 @@
     int mTaskBarViewLightTextColor;
     int mTaskBarViewDarkTextColor;
     int mDisabledTaskBarBackgroundColor;
-    int mMoveTaskTargetStackId = INVALID_STACK_ID;
+    int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
 
     // Header background
     private HighlightColorDrawable mBackground;
@@ -485,12 +486,12 @@
         // current task
         if (mMoveTaskButton != null) {
             if (t.isFreeformTask()) {
-                mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
+                mTaskWindowingMode = WINDOWING_MODE_FULLSCREEN;
                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
                         ? mLightFullscreenIcon
                         : mDarkFullscreenIcon);
             } else {
-                mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
+                mTaskWindowingMode = WINDOWING_MODE_FREEFORM;
                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
                         ? mLightFreeformIcon
                         : mDarkFreeformIcon);
@@ -621,8 +622,8 @@
                     Constants.Metrics.DismissSourceHeaderButton);
         } else if (v == mMoveTaskButton) {
             TaskView tv = Utilities.findParent(this, TaskView.class);
-            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
-                    mMoveTaskTargetStackId, false));
+            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, false,
+                    mTaskWindowingMode, ACTIVITY_TYPE_UNDEFINED));
         } else if (v == mAppInfoView) {
             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
         } else if (v == mAppIconView) {
diff --git a/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java b/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
index bcf4f17..2d7cfb1 100644
--- a/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
+++ b/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
@@ -26,8 +26,8 @@
 
 public class GridTaskViewThumbnail extends TaskViewThumbnail {
 
-    private Path mThumbnailOutline;
-    private Path mRestBackgroundOutline;
+    private final Path mThumbnailOutline = new Path();
+    private final Path mRestBackgroundOutline = new Path();
     // True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
     // be updated.
     private boolean mUpdateThumbnailOutline = true;
@@ -77,47 +77,38 @@
             (int) (mThumbnailRect.width() * mThumbnailScale));
         final int thumbnailHeight = Math.min(viewHeight,
             (int) (mThumbnailRect.height() * mThumbnailScale));
-        // Draw the thumbnail, we only round the bottom corners:
-        //
-        // outerLeft                outerRight
-        //    <----------------------->            mRestBackgroundOutline
-        //    _________________________            (thumbnailWidth < viewWidth)
-        //    |_______________________| outerTop     A ____ B
-        //    |                       |    ↑           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  | C
-        //    \_______________________/    ↓           |__/
-        //  mCornerRadius             outerBottom    E    D
-        //
-        //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
-        //  A _________________________ B
-        //    |                       | C
-        //  F \_______________________/
-        //    E                       D
-        final int outerLeft = 0;
-        final int outerTop = 0;
-        final int outerRight = outerLeft + thumbnailWidth;
-        final int outerBottom = outerTop + thumbnailHeight;
-        mThumbnailOutline = new Path();
-        mThumbnailOutline.moveTo(outerLeft, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerBottom - mCornerRadius);
-        mThumbnailOutline.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius,
-                outerRight, outerBottom, 0, 90, false);
-        mThumbnailOutline.lineTo(outerLeft + mCornerRadius, outerBottom);
-        mThumbnailOutline.arcTo(outerLeft, outerBottom - 2 * mCornerRadius,
-                outerLeft + 2 * mCornerRadius, outerBottom, 90, 90, false);
-        mThumbnailOutline.lineTo(outerLeft, outerTop);
-        mThumbnailOutline.close();
 
         if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+            // Draw the thumbnail, we only round the bottom corners:
+            //
+            // outerLeft                outerRight
+            //    <----------------------->            mRestBackgroundOutline
+            //    _________________________            (thumbnailWidth < viewWidth)
+            //    |_______________________| outerTop     A ____ B
+            //    |                       |    ↑           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  | C
+            //    \_______________________/    ↓           |__/
+            //  mCornerRadius             outerBottom    E    D
+            //
+            //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
+            //  A _________________________ B
+            //    |                       | C
+            //  F \_______________________/
+            //    E                       D
+            final int outerLeft = 0;
+            final int outerTop = 0;
+            final int outerRight = outerLeft + thumbnailWidth;
+            final int outerBottom = outerTop + thumbnailHeight;
+            createThumbnailPath(outerLeft, outerTop, outerRight, outerBottom, mThumbnailOutline);
+
             if (thumbnailWidth < viewWidth) {
                 final int l = Math.max(0, outerRight - mCornerRadius);
                 final int r = outerRight;
                 final int t = outerTop;
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -133,7 +124,7 @@
                 final int r = outerRight;
                 final int t = Math.max(0, thumbnailHeight - mCornerRadius);
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -145,9 +136,26 @@
                 mRestBackgroundOutline.lineTo(l, t); // A
                 mRestBackgroundOutline.close();
             }
+        } else {
+            createThumbnailPath(0, 0, viewWidth, viewHeight, mThumbnailOutline);
         }
     }
 
+    private void createThumbnailPath(int outerLeft, int outerTop, int outerRight, int outerBottom,
+            Path outPath) {
+        outPath.reset();
+        outPath.moveTo(outerLeft, outerTop);
+        outPath.lineTo(outerRight, outerTop);
+        outPath.lineTo(outerRight, outerBottom - mCornerRadius);
+        outPath.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius, outerRight,
+                outerBottom, 0, 90, false);
+        outPath.lineTo(outerLeft + mCornerRadius, outerBottom);
+        outPath.arcTo(outerLeft, outerBottom - 2 * mCornerRadius, outerLeft + 2 * mCornerRadius,
+                outerBottom, 90, 90, false);
+        outPath.lineTo(outerLeft, outerTop);
+        outPath.close();
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         final int titleHeight = getResources().getDimensionPixelSize(
diff --git a/com/android/systemui/stackdivider/DividerView.java b/com/android/systemui/stackdivider/DividerView.java
index 6bfef20..7bcef57 100644
--- a/com/android/systemui/stackdivider/DividerView.java
+++ b/com/android/systemui/stackdivider/DividerView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.stackdivider;
 
+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.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 
@@ -23,7 +26,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -36,8 +38,6 @@
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.VelocityTracker;
@@ -62,8 +62,8 @@
 import com.android.internal.view.SurfaceFlingerVsyncChoreographer;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
 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.activity.UndockingTaskEvent;
@@ -93,7 +93,6 @@
     private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
 
     private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
-    private static final boolean SWAPPING_ENABLED = false;
 
     /**
      * How much the background gets scaled when we are in the minimized dock state.
@@ -153,7 +152,6 @@
     private boolean mEntranceAnimationRunning;
     private boolean mExitAnimationRunning;
     private int mExitStartPosition;
-    private GestureDetector mGestureDetector;
     private boolean mDockedStackMinimized;
     private boolean mHomeStackResizable;
     private boolean mAdjustedForIme;
@@ -295,21 +293,6 @@
                 landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
         mHandle.setAccessibilityDelegate(mHandleDelegate);
-        mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
-            @Override
-            public boolean onSingleTapUp(MotionEvent e) {
-                if (SWAPPING_ENABLED) {
-                    updateDockSide();
-                    SystemServicesProxy ssp = Recents.getSystemServices();
-                    if (mDockSide != WindowManager.DOCKED_INVALID
-                            && !ssp.isRecentsActivityVisible()) {
-                        mWindowManagerProxy.swapTasks();
-                        return true;
-                    }
-                }
-                return false;
-            }
-        });
     }
 
     @Override
@@ -478,7 +461,6 @@
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         convertToScreenCoordinates(event);
-        mGestureDetector.onTouchEvent(event);
         final int action = event.getAction() & MotionEvent.ACTION_MASK;
         switch (action) {
             case MotionEvent.ACTION_DOWN:
@@ -679,7 +661,7 @@
         } else {
             mWindowManagerProxy.maximizeDockedStack();
         }
-        mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
+        mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
     }
 
     private void liftBackground() {
@@ -1015,8 +997,7 @@
         SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
         float dimFraction = getDimFraction(position, closestDismissTarget);
         mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
-                getStackIdForDismissTarget(closestDismissTarget),
-                dimFraction);
+                getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
     }
 
     private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1150,13 +1131,13 @@
         }
     }
 
-    private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
+    private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
         if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
                 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
                         && dockSideBottomRight(mDockSide))) {
-            return StackId.DOCKED_STACK_ID;
+            return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
         } else {
-            return StackId.RECENTS_STACK_ID;
+            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
         }
     }
 
@@ -1210,6 +1191,10 @@
         }
     }
 
+    public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+        saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget());
+    }
+
     public final void onBusEvent(DockedTopTaskEvent event) {
         if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
             mState.growAfterRecentsDrawn = false;
diff --git a/com/android/systemui/stackdivider/WindowManagerProxy.java b/com/android/systemui/stackdivider/WindowManagerProxy.java
index c245126..85a6062 100644
--- a/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.stackdivider;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.view.WindowManager.DOCKED_INVALID;
 
 import android.app.ActivityManager;
@@ -56,7 +55,7 @@
     private final Rect mTouchableRegion = new Rect();
 
     private boolean mDimLayerVisible;
-    private int mDimLayerTargetStack;
+    private int mDimLayerTargetWindowingMode;
     private float mDimLayerAlpha;
 
     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@@ -88,8 +87,7 @@
         @Override
         public void run() {
             try {
-                ActivityManager.getService().moveTasksToFullscreenStack(
-                        DOCKED_STACK_ID, false /* onTop */);
+                ActivityManager.getService().dismissSplitScreenMode(false /* onTop */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to remove stack: " + e);
             }
@@ -100,8 +98,7 @@
         @Override
         public void run() {
             try {
-                ActivityManager.getService().resizeStack(
-                        DOCKED_STACK_ID, null, true, true, false, -1);
+                ActivityManager.getService().dismissSplitScreenMode(true /* onTop */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to resize stack: " + e);
             }
@@ -113,18 +110,7 @@
         public void run() {
             try {
                 WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
-                        mDimLayerTargetStack, mDimLayerAlpha);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to resize stack: " + e);
-            }
-        }
-    };
-
-    private final Runnable mSwapRunnable = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                ActivityManager.getService().swapDockedAndFullscreenStack();
+                        mDimLayerTargetWindowingMode, mDimLayerAlpha);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to resize stack: " + e);
             }
@@ -211,17 +197,13 @@
         return DOCKED_INVALID;
     }
 
-    public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         mDimLayerVisible = visible;
-        mDimLayerTargetStack = targetStackId;
+        mDimLayerTargetWindowingMode = targetWindowingMode;
         mDimLayerAlpha = alpha;
         mExecutor.execute(mDimLayerRunnable);
     }
 
-    public void swapTasks() {
-        mExecutor.execute(mSwapRunnable);
-    }
-
     public void setTouchRegion(Rect region) {
         synchronized (mDockedRect) {
             mTouchableRegion.set(region);
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 7fe7f39..966e789 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -64,10 +64,10 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -436,9 +436,6 @@
         } else {
             minHeight = mNotificationMinHeight;
         }
-        NotificationViewWrapper collapsedWrapper = layout.getVisibleWrapper(
-                NotificationContentView.VISIBLE_TYPE_CONTRACTED);
-        minHeight += collapsedWrapper.getMinHeightIncrease(mUseIncreasedCollapsedHeight);
         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
                 layout.getHeadsUpChild().getId()
                         != com.android.internal.R.id.status_bar_latest_event_content;
@@ -450,11 +447,6 @@
         } else {
             headsUpheight = mMaxHeadsUpHeight;
         }
-        NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
-                NotificationContentView.VISIBLE_TYPE_HEADSUP);
-        if (headsUpWrapper != null) {
-            headsUpheight += headsUpWrapper.getMinHeightIncrease(mUseIncreasedCollapsedHeight);
-        }
         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
                 mNotificationAmbientHeight);
     }
@@ -2024,14 +2016,15 @@
     }
 
     @Override
-    public int getMinHeight() {
-        if (mGuts != null && mGuts.isExposed()) {
+    public int getMinHeight(boolean ignoreTemporaryStates) {
+        if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) {
             return mGuts.getIntrinsicHeight();
-        } else if (isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+        } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp
+                && mHeadsUpManager.isTrackingHeadsUp()) {
                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
             return mChildrenContainer.getMinHeight();
-        } else if (isHeadsUpAllowed() && mIsHeadsUp) {
+        } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) {
             return mHeadsUpHeight;
         }
         NotificationContentView showingLayout = getShowingLayout();
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index efe5e0c..aac9af8 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -151,9 +151,21 @@
     }
 
     /**
-     * @return The minimum content height of this notification.
+     * @return The minimum content height of this notification. This also respects the temporary
+     * states of the view.
      */
     public int getMinHeight() {
+        return getMinHeight(false /* ignoreTemporaryStates */);
+    }
+
+    /**
+     * Get the minimum height of this view.
+     *
+     * @param ignoreTemporaryStates should temporary states be ignored like the guts or heads-up.
+     *
+     * @return The minimum height that this view needs.
+     */
+    public int getMinHeight(boolean ignoreTemporaryStates) {
         return getHeight();
     }
 
diff --git a/com/android/systemui/statusbar/KeyboardShortcuts.java b/com/android/systemui/statusbar/KeyboardShortcuts.java
index d370a63..2d16d22 100644
--- a/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -433,24 +433,27 @@
         // Assist.
         final AssistUtils assistUtils = new AssistUtils(mContext);
         final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
-        PackageInfo assistPackageInfo = null;
-        try {
-            assistPackageInfo = mPackageManager.getPackageInfo(
-                    assistComponent.getPackageName(), 0, userId);
-        } catch (RemoteException e) {
-            Log.e(TAG, "PackageManagerService is dead");
-        }
+        // Not all devices have an assist component.
+        if (assistComponent != null) {
+            PackageInfo assistPackageInfo = null;
+            try {
+                assistPackageInfo = mPackageManager.getPackageInfo(
+                        assistComponent.getPackageName(), 0, userId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManagerService is dead");
+            }
 
-        if (assistPackageInfo != null) {
-            final Icon assistIcon = Icon.createWithResource(
-                    assistPackageInfo.applicationInfo.packageName,
-                    assistPackageInfo.applicationInfo.icon);
+            if (assistPackageInfo != null) {
+                final Icon assistIcon = Icon.createWithResource(
+                        assistPackageInfo.applicationInfo.packageName,
+                        assistPackageInfo.applicationInfo.icon);
 
-            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
-                    mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
-                    assistIcon,
-                    KeyEvent.KEYCODE_UNKNOWN,
-                    KeyEvent.META_META_ON));
+                keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+                        mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
+                        assistIcon,
+                        KeyEvent.KEYCODE_UNKNOWN,
+                        KeyEvent.META_META_ON));
+            }
         }
 
         // Browser.
diff --git a/com/android/systemui/statusbar/NotificationData.java b/com/android/systemui/statusbar/NotificationData.java
index ddc7dd0..d0417b5 100644
--- a/com/android/systemui/statusbar/NotificationData.java
+++ b/com/android/systemui/statusbar/NotificationData.java
@@ -292,8 +292,8 @@
 
             if (mRankingMap != null) {
                 // RankingMap as received from NoMan
-                mRankingMap.getRanking(a.key, mRankingA);
-                mRankingMap.getRanking(b.key, mRankingB);
+                getRanking(a.key, mRankingA);
+                getRanking(b.key, mRankingB);
                 aImportance = mRankingA.getImportance();
                 bImportance = mRankingB.getImportance();
                 aRank = mRankingA.getRank();
@@ -381,7 +381,7 @@
 
     public boolean isAmbient(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.isAmbient();
         }
         return false;
@@ -389,7 +389,7 @@
 
     public int getVisibilityOverride(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getVisibilityOverride();
         }
         return Ranking.VISIBILITY_NO_OVERRIDE;
@@ -397,7 +397,7 @@
 
     public boolean shouldSuppressScreenOff(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return (mTmpRanking.getSuppressedVisualEffects()
                     & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0;
         }
@@ -406,7 +406,7 @@
 
     public boolean shouldSuppressScreenOn(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return (mTmpRanking.getSuppressedVisualEffects()
                     & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON) != 0;
         }
@@ -415,7 +415,7 @@
 
     public int getImportance(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getImportance();
         }
         return NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -423,7 +423,7 @@
 
     public String getOverrideGroupKey(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getOverrideGroupKey();
         }
          return null;
@@ -431,7 +431,7 @@
 
     public List<SnoozeCriterion> getSnoozeCriteria(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getSnoozeCriteria();
         }
         return null;
@@ -439,7 +439,7 @@
 
     public NotificationChannel getChannel(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getChannel();
         }
         return null;
@@ -452,6 +452,9 @@
                 final int N = mEntries.size();
                 for (int i = 0; i < N; i++) {
                     Entry entry = mEntries.valueAt(i);
+                    if (!getRanking(entry.key, mTmpRanking)) {
+                        continue;
+                    }
                     final StatusBarNotification oldSbn = entry.notification.cloneLight();
                     final String overrideGroupKey = getOverrideGroupKey(entry.key);
                     if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
@@ -466,6 +469,19 @@
         filterAndSort();
     }
 
+    /**
+     * Get the ranking from the current ranking map.
+     *
+     * @param key the key to look up
+     * @param outRanking the ranking to populate
+     *
+     * @return {@code true} if the ranking was properly obtained.
+     */
+    @VisibleForTesting
+    protected boolean getRanking(String key, Ranking outRanking) {
+        return mRankingMap.getRanking(key, outRanking);
+    }
+
     // TODO: This should not be public. Instead the Environment should notify this class when
     // anything changed, and this class should call back the UI so it updates itself.
     public void filterAndSort() {
@@ -573,7 +589,7 @@
     }
 
     private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
-        mRankingMap.getRanking(e.key, mTmpRanking);
+        getRanking(e.key, mTmpRanking);
         pw.print(indent);
         pw.println("  [" + i + "] key=" + e.key + " icon=" + e.icon);
         StatusBarNotification n = e.notification;
diff --git a/com/android/systemui/statusbar/NotificationSnooze.java b/com/android/systemui/statusbar/NotificationSnooze.java
index c45ca54..492ab44 100644
--- a/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,8 +16,13 @@
  */
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
@@ -28,12 +33,15 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Typeface;
+import android.metrics.LogMaker;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.text.SpannableString;
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -51,11 +59,23 @@
 public class NotificationSnooze extends LinearLayout
         implements NotificationGuts.GutsContent, View.OnClickListener {
 
+    private static final String TAG = "NotificationSnooze";
     /**
      * If this changes more number increases, more assistant action resId's should be defined for
      * accessibility purposes, see {@link #setSnoozeOptions(List)}
      */
     private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
+    private static final String KEY_DEFAULT_SNOOZE = "default";
+    private static final String KEY_OPTIONS = "options_array";
+    private static final LogMaker OPTIONS_OPEN_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_OPEN);
+    private static final LogMaker OPTIONS_CLOSE_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_CLOSE);
+    private static final LogMaker UNDO_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_UNDO_SNOOZE)
+                    .setType(MetricsEvent.TYPE_ACTION);
     private NotificationGuts mGutsContainer;
     private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
@@ -72,9 +92,31 @@
     private boolean mSnoozing;
     private boolean mExpanded;
     private AnimatorSet mExpandAnimation;
+    private KeyValueListParser mParser;
+
+    private final static int[] sAccessibilityActions = {
+            R.id.action_snooze_shorter,
+            R.id.action_snooze_short,
+            R.id.action_snooze_long,
+            R.id.action_snooze_longer,
+    };
+
+    private MetricsLogger mMetricsLogger = new MetricsLogger();
 
     public NotificationSnooze(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mParser = new KeyValueListParser(',');
+    }
+
+    @VisibleForTesting
+    SnoozeOption getDefaultOption()
+    {
+        return mDefaultOption;
+    }
+
+    @VisibleForTesting
+    void setKeyValueListParser(KeyValueListParser parser) {
+        mParser = parser;
     }
 
     @Override
@@ -96,7 +138,13 @@
         mSnoozeOptions = getDefaultSnoozeOptions();
         createOptionViews();
 
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
     }
 
     @Override
@@ -136,7 +184,7 @@
             SnoozeOption so = mSnoozeOptions.get(i);
             if (so.getAccessibilityAction() != null
                     && so.getAccessibilityAction().getId() == action) {
-                setSelected(so);
+                setSelected(so, true);
                 return true;
             }
         }
@@ -172,17 +220,49 @@
         mSbn = sbn;
     }
 
-    private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+    @VisibleForTesting
+    ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+        final Resources resources = getContext().getResources();
         ArrayList<SnoozeOption> options = new ArrayList<>();
+        try {
+            final String config = Settings.Global.getString(getContext().getContentResolver(),
+                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS);
+            mParser.setString(config);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Bad snooze constants");
+        }
 
-        options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
-        options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
-        mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
-        options.add(mDefaultOption);
-        options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
+        final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
+                resources.getInteger(R.integer.config_notification_snooze_time_default));
+        final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+                resources.getIntArray(R.array.config_notification_snooze_times));
+
+        for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
+            int snoozeTime = snoozeTimes[i];
+            SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]);
+            if (i == 0 || snoozeTime == defaultSnooze) {
+                mDefaultOption = option;
+            }
+            options.add(option);
+        }
         return options;
     }
 
+    @VisibleForTesting
+    int[] parseIntArray(final String key, final int[] defaultArray) {
+        final String value = mParser.getString(key, null);
+        if (value != null) {
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
+        } else {
+            return defaultArray;
+        }
+    }
+
     private SnoozeOption createOption(int minutes, int accessibilityActionId) {
         Resources res = getResources();
         boolean showInHours = minutes >= 60;
@@ -268,12 +348,24 @@
         mExpandAnimation.start();
     }
 
-    private void setSelected(SnoozeOption option) {
+    private void setSelected(SnoozeOption option, boolean userAction) {
         mSelectedOption = option;
         mSelectedOptionText.setText(option.getConfirmation());
         showSnoozeOptions(false);
         hideSelectedOption();
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        if (userAction) {
+            logOptionSelection(MetricsEvent.NOTIFICATION_SELECT_SNOOZE, option);
+        }
+    }
+
+    private void logOptionSelection(int category, SnoozeOption option) {
+        int index = mSnoozeOptions.indexOf(option);
+        long duration = TimeUnit.MINUTES.toMillis(option.getMinutesToSnoozeFor());
+        mMetricsLogger.write(new LogMaker(category)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_INDEX, index)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS, duration));
     }
 
     @Override
@@ -284,13 +376,15 @@
         final int id = v.getId();
         final SnoozeOption tag = (SnoozeOption) v.getTag();
         if (tag != null) {
-            setSelected(tag);
+            setSelected(tag, true);
         } else if (id == R.id.notification_snooze) {
             // Toggle snooze options
             showSnoozeOptions(!mExpanded);
+            mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
         } else {
             // Undo snooze was selected
             undoSnooze(v);
+            mMetricsLogger.write(UNDO_LOG);
         }
     }
 
@@ -321,7 +415,7 @@
     @Override
     public View getContentView() {
         // Reset the view before use
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
         return this;
     }
 
@@ -343,7 +437,7 @@
             return true;
         } else {
             // The view should actually be closed
-            setSelected(mSnoozeOptions.get(0));
+            setSelected(mSnoozeOptions.get(0), false);
             return false; // Return false here so that guts handles closing the view
         }
     }
diff --git a/com/android/systemui/statusbar/SignalClusterView.java b/com/android/systemui/statusbar/SignalClusterView.java
index 759d2cf..a7fb61a 100644
--- a/com/android/systemui/statusbar/SignalClusterView.java
+++ b/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,14 +16,15 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
@@ -50,14 +51,15 @@
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
 public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
-        SecurityController.SecurityControllerCallback, Tunable,
-        DarkReceiver {
+        SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
 
     static final String TAG = "SignalClusterView";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -73,6 +75,7 @@
 
     private boolean mNoSimsVisible = false;
     private boolean mVpnVisible = false;
+    private boolean mSimDetected;
     private int mVpnIconId = 0;
     private int mLastVpnIconId = -1;
     private boolean mEthernetVisible = false;
@@ -148,6 +151,8 @@
         mIconScaleFactor = typedValue.getFloat();
         mNetworkController = Dependency.get(NetworkController.class);
         mSecurityController = Dependency.get(SecurityController.class);
+        addOnAttachStateChangeListener(
+                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
         updateActivityEnabled();
     }
 
@@ -327,8 +332,9 @@
     }
 
     @Override
-    public void setNoSims(boolean show) {
+    public void setNoSims(boolean show, boolean simDetected) {
         mNoSimsVisible = show && !mBlockMobile;
+        mSimDetected = simDetected;
         apply();
     }
 
@@ -548,6 +554,23 @@
         if (mNoSimsVisible) {
             mIconLogger.onIconShown(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.VISIBLE);
+            if (!Objects.equals(mSimDetected, mNoSimsCombo.getTag())) {
+                mNoSimsCombo.setTag(mSimDetected);
+                if (mSimDetected) {
+                    SignalDrawable d = new SignalDrawable(mNoSims.getContext());
+                    d.setDarkIntensity(0);
+                    mNoSims.setImageDrawable(d);
+                    mNoSims.setImageLevel(SignalDrawable.getEmptyState(4));
+
+                    SignalDrawable dark = new SignalDrawable(mNoSims.getContext());
+                    dark.setDarkIntensity(1);
+                    mNoSimsDark.setImageDrawable(dark);
+                    mNoSimsDark.setImageLevel(SignalDrawable.getEmptyState(4));
+                } else {
+                    mNoSims.setImageResource(R.drawable.stat_sys_no_sims);
+                    mNoSimsDark.setImageResource(R.drawable.stat_sys_no_sims);
+                }
+            }
         } else {
             mIconLogger.onIconHidden(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.GONE);
diff --git a/com/android/systemui/statusbar/StatusBarIconView.java b/com/android/systemui/statusbar/StatusBarIconView.java
index 2cff79d..6cfd42f 100644
--- a/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/com/android/systemui/statusbar/StatusBarIconView.java
@@ -43,6 +43,7 @@
 import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
@@ -142,6 +143,7 @@
     private float[] mMatrix;
     private ColorMatrixColorFilter mMatrixColorFilter;
     private boolean mIsInShelf;
+    private Runnable mLayoutRunnable;
 
     public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
         this(context, slot, sbn, false);
@@ -796,6 +798,24 @@
         }
     }
 
+    /**
+     * This method returns the drawing rect for the view which is different from the regular
+     * drawing rect, since we layout all children at position 0 and usually the translation is
+     * neglected. The standard implementation doesn't account for translation.
+     *
+     * @param outRect The (scrolled) drawing bounds of the view.
+     */
+    @Override
+    public void getDrawingRect(Rect outRect) {
+        super.getDrawingRect(outRect);
+        float translationX = getTranslationX();
+        float translationY = getTranslationY();
+        outRect.left += translationX;
+        outRect.right += translationX;
+        outRect.top += translationY;
+        outRect.bottom += translationY;
+    }
+
     public void setIsInShelf(boolean isInShelf) {
         mIsInShelf = isInShelf;
     }
@@ -804,6 +824,19 @@
         return mIsInShelf;
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mLayoutRunnable != null) {
+            mLayoutRunnable.run();
+            mLayoutRunnable = null;
+        }
+    }
+
+    public void executeOnLayout(Runnable runnable) {
+        mLayoutRunnable = runnable;
+    }
+
     public interface OnVisibilityChangedListener {
         void onVisibilityChanged(int newVisibility);
     }
diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java
index 7e08d56..f5c77f2 100644
--- a/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -15,7 +15,13 @@
  */
 package com.android.systemui.statusbar.car;
 
-import android.app.ActivityManager.StackId;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -101,7 +107,7 @@
         }
     }
 
-    public void taskChanged(String packageName, int stackId) {
+    public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) {
         // If the package name belongs to a filter, then highlight appropriate button in
         // the navigation bar.
         if (mFacetPackageMap.containsKey(packageName)) {
@@ -115,9 +121,11 @@
         }
 
         // Set up the persistent docked task if needed.
-        if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask()
-                && stackId != StackId.HOME_STACK_ID) {
-            mStatusBar.startActivityOnStack(mPersistentTaskIntent, StackId.DOCKED_STACK_ID);
+        boolean isHomeTask =
+                taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
+        if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) {
+            mStatusBar.startActivityOnStack(mPersistentTaskIntent,
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         }
     }
 
@@ -375,13 +383,15 @@
         // rather than the "preferred/last run" app.
         intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);
 
-        int stackId = StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+        int windowingMode = WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+        int activityType = ACTIVITY_TYPE_UNDEFINED;
         if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
-            stackId = StackId.HOME_STACK_ID;
+            windowingMode = WINDOWING_MODE_UNDEFINED;
+            activityType = ACTIVITY_TYPE_HOME;
         }
 
         setCurrentFacet(index);
-        mStatusBar.startActivityOnStack(intent, stackId);
+        mStatusBar.startActivityOnStack(intent, windowingMode, activityType);
     }
 
     /**
@@ -391,6 +401,7 @@
      */
     private void onFacetLongClicked(Intent intent, int index) {
         setCurrentFacet(index);
-        mStatusBar.startActivityOnStack(intent, StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+        mStatusBar.startActivityOnStack(intent,
+                WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED);
     }
 }
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index 680f693..59d3e0a 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -314,7 +314,7 @@
             ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
             if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
                 mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
-                        runningTaskInfo.stackId);
+                        runningTaskInfo);
             }
         }
     }
@@ -378,9 +378,10 @@
         return result;
     }
 
-    public int startActivityOnStack(Intent intent, int stackId) {
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(stackId);
+    public int startActivityOnStack(Intent intent, int windowingMode, int activityType) {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(windowingMode);
+        options.setLaunchActivityType(activityType);
         return startActivityWithOptions(intent, options.toBundle());
     }
 
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 9bfa7a9..bb979eb 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -25,7 +25,6 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
@@ -48,7 +47,6 @@
 
     private int mContentHeight;
     private int mMinHeightHint;
-    private boolean mColorized;
 
     protected NotificationTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
@@ -164,9 +162,7 @@
     public void onContentUpdated(ExpandableNotificationRow row) {
         // Reinspect the notification. Before the super call, because the super call also updates
         // the transformation types and we need to have our values set by then.
-        StatusBarNotification sbn = row.getStatusBarNotification();
-        resolveTemplateViews(sbn);
-        mColorized = sbn.getNotification().isColorized();
+        resolveTemplateViews(row.getStatusBarNotification());
         super.onContentUpdated(row);
     }
 
@@ -269,17 +265,6 @@
         updateActionOffset();
     }
 
-    @Override
-    public int getMinHeightIncrease(boolean useIncreasedCollapsedHeight) {
-        if (mColorized) {
-            int dimen = useIncreasedCollapsedHeight
-                    ? R.dimen.notification_height_increase_colorized_increased
-                    : R.dimen.notification_height_increase_colorized;
-            return mRow.getResources().getDimensionPixelSize(dimen);
-        }
-        return super.getMinHeightIncrease(useIncreasedCollapsedHeight);
-    }
-
     private void updateActionOffset() {
         if (mActionsContainer != null) {
             // We should never push the actions higher than they are in the headsup view.
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 085bce9..5200d69 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,14 +190,4 @@
     public boolean disallowSingleClick(float x, float y) {
         return false;
     }
-
-    /**
-     * Get the amount that the minheight is allowed to be increased based on this layout.
-     *
-     * @param increasedHeight is the view allowed to show even bigger, i.e for messaging layouts
-     * @return
-     */
-    public int getMinHeightIncrease(boolean increasedHeight) {
-        return 0;
-    }
 }
diff --git a/com/android/systemui/statusbar/phone/BarTransitions.java b/com/android/systemui/statusbar/phone/BarTransitions.java
index 1f44abe..4bca797 100644
--- a/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -50,7 +50,7 @@
     public static final int MODE_LIGHTS_OUT_TRANSPARENT = 6;
 
     public static final int LIGHTS_IN_DURATION = 250;
-    public static final int LIGHTS_OUT_DURATION = 750;
+    public static final int LIGHTS_OUT_DURATION = 1500;
     public static final int BACKGROUND_DURATION = 200;
 
     private final String mTag;
diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java
index 021b451..8afb849 100644
--- a/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -384,7 +384,7 @@
             if (mDozeParameters.getAlwaysOn()) {
                 // Setting power states can happen after we push out the frame. Make sure we
                 // stay fully opaque until the power state request reaches the lower levels.
-                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 30);
+                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
             }
         }
     };
diff --git a/com/android/systemui/statusbar/phone/LightBarController.java b/com/android/systemui/statusbar/phone/LightBarController.java
index 533771a..d226fed 100644
--- a/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/com/android/systemui/statusbar/phone/LightBarController.java
@@ -264,8 +264,10 @@
         pw.println(" StatusBarTransitionsController:");
         mStatusBarIconController.getTransitionsController().dump(fd, pw, args);
         pw.println();
-        pw.println(" NavigationBarTransitionsController:");
-        mNavigationBarController.dump(fd, pw, args);
-        pw.println();
+        if (mNavigationBarController != null) {
+            pw.println(" NavigationBarTransitionsController:");
+            mNavigationBarController.dump(fd, pw, args);
+            pw.println();
+        }
     }
 }
diff --git a/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index cfe0a4a..6d3bc1d 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -437,7 +437,7 @@
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
-        mStatusBar.checkUserAutohide(v, event);
+        mStatusBar.checkUserAutohide(event);
         return false;
     }
 
diff --git a/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 41a69b4..40fe50f 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -4,10 +4,8 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Icon;
 import android.support.annotation.NonNull;
 import android.support.v4.util.ArrayMap;
-import android.support.v4.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -269,18 +267,26 @@
      */
     private void applyNotificationIconsTint() {
         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
-            boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
-            int color = StatusBarIconView.NO_COLOR;
-            boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
-            if (colorize) {
-                color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+            final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
+            if (iv.getWidth() != 0) {
+                updateTintForIcon(iv);
+            } else {
+                iv.executeOnLayout(() -> updateTintForIcon(iv));
             }
-            v.setStaticDrawableColor(color);
-            v.setDecorColor(mIconTint);
         }
     }
 
+    private void updateTintForIcon(StatusBarIconView v) {
+        boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+        int color = StatusBarIconView.NO_COLOR;
+        boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
+        if (colorize) {
+            color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+        }
+        v.setStaticDrawableColor(color);
+        v.setDecorColor(mIconTint);
+    }
+
     public void setDark(boolean dark) {
         mNotificationIcons.setDark(dark, false, 0);
         mShelfIcons.setDark(dark, false, 0);
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 078e818..7b11ace 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -509,7 +509,8 @@
             if (row.isRemoved()) {
                 continue;
             }
-            availableSpace -= child.getMinHeight() + notificationPadding;
+            availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
+                    + notificationPadding;
             if (availableSpace >= 0 && count < maximum) {
                 count++;
             } else if (availableSpace > -shelfSize) {
@@ -666,7 +667,7 @@
             return false;
         }
         initDownStates(event);
-        if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+        if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
             mIsExpansionFromHeadsUp = true;
             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 4ae1393..9c837ed 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+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 android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.AlarmManager;
 import android.app.AlarmManager.AlarmClockInfo;
@@ -523,12 +527,18 @@
         mCurrentNotifs.clear();
         mUiOffloadThread.submit(() -> {
             try {
-                int focusedId = ActivityManager.getService().getFocusedStackId();
-                if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
-                    checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+                final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo();
+                if (focusedStack != null) {
+                    final int windowingMode =
+                            focusedStack.configuration.windowConfiguration.getWindowingMode();
+                    if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                            || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+                        checkStack(focusedStack, notifs, noMan, pm);
+                    }
                 }
                 if (mDockedStackExists) {
-                    checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
+                    checkStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED,
+                            notifs, noMan, pm);
                 }
             } catch (RemoteException e) {
                 e.rethrowFromSystemServer();
@@ -539,10 +549,19 @@
         });
     }
 
-    private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs,
+    private void checkStack(int windowingMode, int activityType,
+            ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) {
+        try {
+            final StackInfo info =
+                    ActivityManager.getService().getStackInfo(windowingMode, activityType);
+            checkStack(info, notifs, noMan, pm);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+    private void checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs,
             NotificationManager noMan, IPackageManager pm) {
         try {
-            StackInfo info = ActivityManager.getService().getStackInfo(stackId);
             if (info == null || info.topActivity == null) return;
             String pkg = info.topActivity.getPackageName();
             if (!hasNotif(notifs, pkg, info.userId)) {
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index efc8d8b..54be857 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -20,6 +20,7 @@
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -37,7 +38,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -69,9 +69,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
@@ -105,10 +102,10 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -127,13 +124,11 @@
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewParent;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
 import android.widget.DateTimeView;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
@@ -259,7 +254,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.Stack;
@@ -279,11 +273,9 @@
             = SystemProperties.getBoolean("debug.child_notifs", true);
     public static final boolean FORCE_REMOTE_INPUT_HISTORY =
             SystemProperties.getBoolean("debug.force_remoteinput_history", false);
-    private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+    private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
 
-    protected static final int MSG_SHOW_RECENT_APPS = 1019;
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
-    protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
     protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
     protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
     protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
@@ -339,7 +331,7 @@
 
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
-    private static final long AUTOHIDE_TIMEOUT_MS = 3000;
+    private static final long AUTOHIDE_TIMEOUT_MS = 2250;
 
     /** The minimum delay in ms between reports of notification visibility. */
     private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
@@ -365,10 +357,6 @@
     /** If true, the lockscreen will show a distinct wallpaper */
     private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
 
-    /* If true, the device supports freeform window management.
-     * This affects the status bar UI. */
-    private static final boolean FREEFORM_WINDOW_MANAGEMENT;
-
     /**
      * How long to wait before auto-dismissing a notification that was kept for remote input, and
      * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
@@ -387,19 +375,14 @@
 
     static {
         boolean onlyCoreApps;
-        boolean freeformWindowManagement;
         try {
             IPackageManager packageManager =
                     IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
             onlyCoreApps = packageManager.isOnlyCoreApps();
-            freeformWindowManagement = packageManager.hasSystemFeature(
-                    PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, 0);
         } catch (RemoteException e) {
             onlyCoreApps = false;
-            freeformWindowManagement = false;
         }
         ONLY_CORE_APPS = onlyCoreApps;
-        FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
     }
 
     /**
@@ -410,17 +393,17 @@
     protected boolean mShowLockscreenNotifications;
     protected boolean mAllowLockscreenRemoteInput;
 
-    PhoneStatusBarPolicy mIconPolicy;
+    private PhoneStatusBarPolicy mIconPolicy;
 
-    VolumeComponent mVolumeComponent;
-    BrightnessMirrorController mBrightnessMirrorController;
+    private VolumeComponent mVolumeComponent;
+    private BrightnessMirrorController mBrightnessMirrorController;
     protected FingerprintUnlockController mFingerprintUnlockController;
-    LightBarController mLightBarController;
+    private LightBarController mLightBarController;
     protected LockscreenWallpaper mLockscreenWallpaper;
 
-    int mNaturalBarHeight = -1;
+    private int mNaturalBarHeight = -1;
 
-    Point mCurrentDisplaySize = new Point();
+    private final Point mCurrentDisplaySize = new Point();
 
     protected StatusBarWindowView mStatusBarWindow;
     protected PhoneStatusBarView mStatusBarView;
@@ -431,15 +414,13 @@
     private boolean mWakeUpComingFromTouch;
     private PointF mWakeUpTouchLocation;
 
-    int mPixelFormat;
-    Object mQueueLock = new Object();
+    private final Object mQueueLock = new Object();
 
     protected StatusBarIconController mIconController;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
-    View mExpandedContents;
-    TextView mNotificationPanelDebugText;
+    private TextView mNotificationPanelDebugText;
 
     /**
      * {@code true} if notifications not part of a group should by default be rendered in their
@@ -452,12 +433,10 @@
     private QSPanel mQSPanel;
 
     // top bar
-    protected KeyguardStatusBarView mKeyguardStatusBar;
-    boolean mLeaveOpenOnKeyguardHide;
+    private KeyguardStatusBarView mKeyguardStatusBar;
+    private boolean mLeaveOpenOnKeyguardHide;
     KeyguardIndicationController mKeyguardIndicationController;
 
-    // Keyguard is going away soon.
-    private boolean mKeyguardGoingAway;
     // Keyguard is actually fading away now.
     protected boolean mKeyguardFadingAway;
     protected long mKeyguardFadingAwayDelay;
@@ -469,25 +448,19 @@
 
     private View mReportRejectedTouch;
 
-    int mMaxAllowedKeyguardNotifications;
+    private int mMaxAllowedKeyguardNotifications;
 
-    boolean mExpandedVisible;
+    private boolean mExpandedVisible;
 
-    // the tracker view
-    int mTrackingPosition; // the position of the top of the tracking view.
-
-    // Tracking finger for opening/closing.
-    boolean mTracking;
-
-    int[] mAbsPos = new int[2];
-    ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+    private final int[] mAbsPos = new int[2];
+    private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
     // for disabling the status bar
-    int mDisabled1 = 0;
-    int mDisabled2 = 0;
+    private int mDisabled1 = 0;
+    private int mDisabled2 = 0;
 
     // tracking calls to View.setSystemUiVisibility()
-    int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+    private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
     private final Rect mLastFullscreenStackBounds = new Rect();
     private final Rect mLastDockedStackBounds = new Rect();
     private final Rect mTmpRect = new Rect();
@@ -495,7 +468,7 @@
     // last value sent to window manager
     private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
 
-    DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
 
     // XXX: gesture research
     private final GestureRecorder mGestureRec = DEBUG_GESTURES
@@ -507,14 +480,17 @@
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
-    private boolean mUserSetup = false;
-    private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
+    @VisibleForTesting
+    protected boolean mUserSetup = false;
+    private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
         @Override
         public void onUserSetupChanged() {
             final boolean userSetup = mDeviceProvisionedController.isUserSetup(
                     mDeviceProvisionedController.getCurrentUser());
-            if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " +
-                    "userSetup=%s mUserSetup=%s", userSetup, mUserSetup));
+            if (MULTIUSER_DEBUG) {
+                Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
+                        userSetup, mUserSetup));
+            }
 
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
@@ -528,7 +504,7 @@
         }
     };
 
-    protected H mHandler = createHandler();
+    protected final H mHandler = createHandler();
     final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange) {
@@ -537,8 +513,6 @@
                     && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
                     mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                     Settings.Global.HEADS_UP_OFF);
-            mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt(
-                    mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 0);
             Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
             if (wasUsing != mUseHeadsUp) {
                 if (!mUseHeadsUp) {
@@ -566,26 +540,21 @@
         }
     };
 
-    private boolean mWaitingForKeyguardExit;
     protected boolean mDozing;
     private boolean mDozingRequested;
     protected boolean mScrimSrcModeEnabled;
 
-    public static final Interpolator ALPHA_IN = Interpolators.ALPHA_IN;
-    public static final Interpolator ALPHA_OUT = Interpolators.ALPHA_OUT;
-
     protected BackDropView mBackdrop;
     protected ImageView mBackdropFront, mBackdropBack;
-    protected PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
-    protected PorterDuffXfermode mSrcOverXferMode =
+    protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
+    protected final PorterDuffXfermode mSrcOverXferMode =
             new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
 
     private MediaSessionManager mMediaSessionManager;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
-    private MediaController.Callback mMediaListener
-            = new MediaController.Callback() {
+    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             super.onPlaybackStateChanged(state);
@@ -607,17 +576,6 @@
         }
     };
 
-    private final OnChildLocationsChangedListener mOnChildLocationsChangedListener =
-            new OnChildLocationsChangedListener() {
-        @Override
-        public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) {
-            userActivity();
-        }
-    };
-
-    private int mDisabledUnmodified1;
-    private int mDisabledUnmodified2;
-
     /** Keys of notifications currently visible to the user. */
     private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
             new ArraySet<>();
@@ -644,15 +602,6 @@
     private boolean mWereIconsJustHidden;
     private boolean mBouncerWasShowingWhenHidden;
 
-    public boolean isStartedGoingToSleep() {
-        return mStartedGoingToSleep;
-    }
-
-    /**
-     * If set, the device has started going to sleep but isn't fully non-interactive yet.
-     */
-    protected boolean mStartedGoingToSleep;
-
     private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
             new OnChildLocationsChangedListener() {
                 @Override
@@ -686,7 +635,6 @@
         @Override
         public void run() {
             mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
-            final String mediaKey = getCurrentMediaNotificationKey();
 
             // 1. Loop over mNotificationData entries:
             //   A. Keep list of visible notifications.
@@ -743,10 +691,10 @@
     private boolean mKeyguardRequested;
     private boolean mIsKeyguard;
     private LogMaker mStatusBarStateLog;
-    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     protected NotificationIconAreaController mNotificationIconAreaController;
     private boolean mReinflateNotificationsOnUserSwitched;
-    private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
+    private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
     private String mKeyToRemoveOnGutsClosed;
@@ -769,20 +717,21 @@
             goToLockedShade(null);
         }
     };
-    private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
-            = new HashMap<>();
+    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+            mTmpChildOrderMap = new HashMap<>();
     private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
 
-    private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onDreamingStateChanged(boolean dreaming) {
-            if (dreaming) {
-                maybeEscalateHeadsUp();
-            }
-        }
-    };
+    private final KeyguardUpdateMonitorCallback mUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onDreamingStateChanged(boolean dreaming) {
+                    if (dreaming) {
+                        maybeEscalateHeadsUp();
+                    }
+                }
+            };
 
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
@@ -861,10 +810,6 @@
 
         mRecents = getComponent(Recents.class);
 
-        final Configuration currentConfig = res.getConfiguration();
-        mLocale = currentConfig.locale;
-        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
-
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
@@ -994,9 +939,6 @@
         Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
-    protected void createIconController() {
-    }
-
     // ================================================================================
     // Constructing the view
     // ================================================================================
@@ -1012,16 +954,14 @@
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
-        mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
-                R.id.notification_panel);
-        mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
-                R.id.notification_stack_scroller);
+        mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+        mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
         mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
                 R.id.notification_container_parent));
-        mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
+        mKeyguardStatusBar = mStatusBarWindow.findViewById(R.id.keyguard_header);
 
         mNotificationIconAreaController = SystemUIFactory.getInstance()
                 .createNotificationIconAreaController(context, this);
@@ -1057,10 +997,10 @@
         mNotificationData.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
+        putComponent(HeadsUpManager.class, mHeadsUpManager);
 
         if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
-                    R.id.header_debug_info);
+            mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
         }
 
@@ -1074,9 +1014,6 @@
             // no window manager? good luck with that
         }
 
-        // figure out which pixel-format to use for the status bar.
-        mPixelFormat = PixelFormat.OPAQUE;
-
         mStackScroller.setLongPressListener(getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
@@ -1086,11 +1023,10 @@
 
         inflateEmptyShadeView();
         inflateDismissView();
-        mExpandedContents = mStackScroller;
 
-        mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop);
-        mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
-        mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
+        mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
+        mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
+        mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back);
 
         if (ENABLE_LOCKSCREEN_WALLPAPER) {
             mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
@@ -1098,8 +1034,8 @@
 
         mKeyguardIndicationController =
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
-                (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
-                mNotificationPanel.getLockIcon());
+                        mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
+                        mNotificationPanel.getLockIcon());
         mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
 
 
@@ -1130,8 +1066,8 @@
             mNavigationBar.setLightBarController(mLightBarController);
         }
 
-        ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
-        ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
+        ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
+        ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
         View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
         mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController,
                 scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
@@ -1141,13 +1077,10 @@
                     }
                 });
         if (mScrimSrcModeEnabled) {
-            Runnable runnable = new Runnable() {
-                @Override
-                public void run() {
-                    boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
-                    mScrimController.setDrawBehindAsSrc(asSrc);
-                    mStackScroller.setDrawBackgroundAsSrc(asSrc);
-                }
+            Runnable runnable = () -> {
+                boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
+                mScrimController.setDrawBehindAsSrc(asSrc);
+                mStackScroller.setDrawBackgroundAsSrc(asSrc);
             };
             mBackdrop.setOnVisibilityChangedRunnable(runnable);
             runnable.run();
@@ -1169,11 +1102,11 @@
         if (container != null) {
             FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
             ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
-                    Dependency.get(ExtensionController.class).newExtension(QS.class)
+                    Dependency.get(ExtensionController.class)
+                            .newExtension(QS.class)
                             .withPlugin(QS.class)
-                            .withFeature(
-                                    PackageManager.FEATURE_AUTOMOTIVE, () -> new CarQSFragment())
-                            .withDefault(() -> new QSFragment())
+                            .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
+                            .withDefault(QSFragment::new)
                             .build());
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mIconController);
@@ -1275,7 +1208,7 @@
      */
     protected View.OnTouchListener getStatusBarWindowTouchListener() {
         return (v, event) -> {
-            checkUserAutohide(v, event);
+            checkUserAutohide(event);
             checkRemoteInputOutside(event);
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 if (mExpandedVisible) {
@@ -1379,8 +1312,7 @@
 
     public static SignalClusterView reinflateSignalCluster(View view) {
         Context context = view.getContext();
-        SignalClusterView signalCluster =
-                (SignalClusterView) view.findViewById(R.id.signal_cluster);
+        SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster);
         if (signalCluster != null) {
             ViewParent parent = signalCluster.getParent();
             if (parent instanceof ViewGroup) {
@@ -1420,20 +1352,17 @@
 
         mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
-        mDismissView.setOnButtonClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
-                clearAllNotifications();
-            }
+        mDismissView.setOnButtonClickListener(v -> {
+            mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
+            clearAllNotifications();
         });
         mStackScroller.setDismissView(mDismissView);
     }
 
     protected void createUserSwitcher() {
         mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
-                mKeyguardStatusBar, mNotificationPanel);
+                mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mKeyguardStatusBar,
+                mNotificationPanel);
     }
 
     protected void inflateStatusBarWindow(Context context) {
@@ -1446,7 +1375,7 @@
         // animate-swipe all dismissable notifications, then animate the shade closed
         int numChildren = mStackScroller.getChildCount();
 
-        final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
+        final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
         final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
         for (int i = 0; i < numChildren; i++) {
             final View child = mStackScroller.getChildAt(i);
@@ -1486,20 +1415,18 @@
             return;
         }
 
-        addPostCollapseAction(new Runnable() {
-            @Override
-            public void run() {
-                mStackScroller.setDismissAllInProgress(false);
-                for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
-                    if (mStackScroller.canChildBeDismissed(rowToRemove)) {
-                        removeNotification(rowToRemove.getEntry().key, null);
-                    } else {
-                        rowToRemove.resetTranslation();
-                    }
+        addPostCollapseAction(() -> {
+            mStackScroller.setDismissAllInProgress(false);
+            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+                if (mStackScroller.canChildBeDismissed(rowToRemove)) {
+                    removeNotification(rowToRemove.getEntry().key, null);
+                } else {
+                    rowToRemove.resetTranslation();
                 }
-                try {
-                    mBarService.onClearAllNotifications(mCurrentUserId);
-                } catch (Exception ex) { }
+            }
+            try {
+                mBarService.onClearAllNotifications(mCurrentUserId);
+            } catch (Exception ex) {
             }
         });
 
@@ -1508,13 +1435,15 @@
     }
 
     private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
-        Runnable animationFinishAction = new Runnable() {
-            @Override
-            public void run() {
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-            }
+        Runnable animationFinishAction = () -> {
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
         };
 
+        if (hideAnimatedList.isEmpty()) {
+            animationFinishAction.run();
+            return;
+        }
+
         // let's disable our normal animations
         mStackScroller.setDismissAllInProgress(true);
 
@@ -1631,10 +1560,6 @@
         SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
     }
 
-    public UserHandle getCurrentUserHandle() {
-        return new UserHandle(mCurrentUserId);
-    }
-
     public void addNotification(StatusBarNotification notification, RankingMap ranking)
             throws InflationException {
         String key = notification.getKey();
@@ -1745,7 +1670,7 @@
         boolean deferRemoval = false;
         abortExistingInflation(key);
         if (mHeadsUpManager.isHeadsUp(key)) {
-            // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
+            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
             // sending look longer than it takes.
             // Also we should not defer the removal if reordering isn't allowed since otherwise
             // some notifications can't disappear before the panel is closed.
@@ -1771,9 +1696,7 @@
                 newHistory = new CharSequence[1];
             } else {
                 newHistory = new CharSequence[oldHistory.length + 1];
-                for (int i = 0; i < oldHistory.length; i++) {
-                    newHistory[i + 1] = oldHistory[i];
-                }
+                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
             }
             newHistory[0] = String.valueOf(entry.remoteInputText);
             b.setRemoteInputHistory(newHistory);
@@ -1832,7 +1755,7 @@
             mStackScroller.cleanUpViewState(entry.row);
         }
         // Let's remove the children if this was a summary
-        handleGroupSummaryRemoved(key, ranking);
+        handleGroupSummaryRemoved(key);
         StatusBarNotification old = removeNotificationViews(key, ranking);
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
@@ -1856,12 +1779,10 @@
      *
      * This also ensures that the animation looks nice and only consists of a single disappear
      * animation instead of multiple.
+     *  @param key the key of the notification was removed
      *
-     * @param key the key of the notification was removed
-     * @param ranking the current ranking
      */
-    private void handleGroupSummaryRemoved(String key,
-            RankingMap ranking) {
+    private void handleGroupSummaryRemoved(String key) {
         Entry entry = mNotificationData.get(key);
         if (entry != null && entry.row != null
                 && entry.row.isSummaryWithChildren()) {
@@ -1872,15 +1793,13 @@
             }
             List<ExpandableNotificationRow> notificationChildren =
                     entry.row.getNotificationChildren();
-            ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
             for (int i = 0; i < notificationChildren.size(); i++) {
                 ExpandableNotificationRow row = notificationChildren.get(i);
                 if ((row.getStatusBarNotification().getNotification().flags
                         & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                    // the child is a forground service notification which we can't remove!
+                    // the child is a foreground service notification which we can't remove!
                     continue;
                 }
-                toRemove.add(row);
                 row.setKeepInParent(true);
                 // we need to set this state earlier as otherwise we might generate some weird
                 // animations
@@ -1900,7 +1819,9 @@
         final int id = n.getId();
         final int userId = n.getUserId();
         try {
-            mBarService.onNotificationClear(pkg, tag, id, userId);
+            // TODO: record actual dismissal surface
+            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
+                    NotificationStats.DISMISSAL_OTHER);
             if (FORCE_REMOTE_INPUT_HISTORY
                     && mKeysKeptForRemoteInput.contains(n.getKey())) {
                 mKeysKeptForRemoteInput.remove(n.getKey());
@@ -1923,19 +1844,14 @@
 
         // Do not modify the notifications during collapse.
         if (isCollapsing()) {
-            addPostCollapseAction(new Runnable() {
-                @Override
-                public void run() {
-                    updateNotificationShade();
-                }
-            });
+            addPostCollapseAction(this::updateNotificationShade);
             return;
         }
 
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
         final int N = activeNotifications.size();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             Entry ent = activeNotifications.get(i);
             if (ent.row.isDismissed() || ent.row.isRemoved()) {
                 // we don't want to update removed notifications because they could
@@ -1979,7 +1895,8 @@
 
         for (ExpandableNotificationRow remove : toRemove) {
             if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
-                // we are only transfering this notification to its parent, don't generate an animation
+                // we are only transferring this notification to its parent, don't generate an
+                // animation
                 mStackScroller.setChildTransferInProgress(true);
             }
             if (remove.isSummaryWithChildren()) {
@@ -1991,7 +1908,7 @@
 
         removeNotificationChildren();
 
-        for (int i=0; i<toShow.size(); i++) {
+        for (int i = 0; i < toShow.size(); i++) {
             View v = toShow.get(i);
             if (v.getParent() == null) {
                 mVisualStabilityManager.notifyViewAddition(v);
@@ -2065,6 +1982,7 @@
         mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
                 && (mUserSetup || mUserSwitcherController == null
                         || !mUserSwitcherController.isSimpleUserSwitcher())
+                && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0)
                 && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
                 && !mDozing
                 && !ONLY_CORE_APPS);
@@ -2101,7 +2019,7 @@
                 }
             }
 
-            // Finally after removing and adding has been beformed we can apply the order.
+            // Finally after removing and adding has been performed we can apply the order.
             orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
         }
         if (orderChanged) {
@@ -2274,10 +2192,11 @@
             MediaController controller = null;
             for (int i = 0; i < N; i++) {
                 final Entry entry = activeNotifications.get(i);
+
                 if (isMediaNotification(entry)) {
                     final MediaSession.Token token =
-                            entry.notification.getNotification().extras
-                            .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+                            entry.notification.getNotification().extras.getParcelable(
+                                    Notification.EXTRA_MEDIA_SESSION);
                     if (token != null) {
                         MediaController aController = new MediaController(mContext, token);
                         if (PlaybackState.STATE_PLAYING ==
@@ -2315,7 +2234,7 @@
                                 if (entry.notification.getPackageName().equals(pkg)) {
                                     if (DEBUG_MEDIA) {
                                         Log.v(TAG, "DEBUG_MEDIA: found controller matching "
-                                            + entry.notification.getKey());
+                                                + entry.notification.getKey());
                                     }
                                     controller = aController;
                                     mediaNotification = entry;
@@ -2366,12 +2285,8 @@
     }
 
     private boolean isPlaybackActive(int state) {
-        if (state != PlaybackState.STATE_STOPPED
-                && state != PlaybackState.STATE_ERROR
-                && state != PlaybackState.STATE_NONE) {
-            return true;
-        }
-        return false;
+        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+                && state != PlaybackState.STATE_NONE;
     }
 
     private void clearCurrentMediaNotification() {
@@ -2396,7 +2311,7 @@
     /**
      * Hide the album artwork that is fading out and release its bitmap.
      */
-    protected Runnable mHideBackdropFront = new Runnable() {
+    protected final Runnable mHideBackdropFront = new Runnable() {
         @Override
         public void run() {
             if (DEBUG_MEDIA) {
@@ -2548,14 +2463,11 @@
                             .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
                             .setDuration(300)
                             .setStartDelay(0)
-                            .withEndAction(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mBackdrop.setVisibility(View.GONE);
-                                    mBackdropFront.animate().cancel();
-                                    mBackdropBack.setImageDrawable(null);
-                                    mHandler.post(mHideBackdropFront);
-                                }
+                            .withEndAction(() -> {
+                                mBackdrop.setVisibility(View.GONE);
+                                mBackdropFront.animate().cancel();
+                                mBackdropBack.setImageDrawable(null);
+                                mHandler.post(mHideBackdropFront);
                             });
                     if (mKeyguardFadingAway) {
                         mBackdrop.animate()
@@ -2586,8 +2498,6 @@
     @Override
     public void disable(int state1, int state2, boolean animate) {
         animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
-        mDisabledUnmodified1 = state1;
-        mDisabledUnmodified2 = state2;
         final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
         mDisabled1 = state1;
@@ -2623,8 +2533,13 @@
         flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_CLOCK))                 ? '!' : ' ');
         flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH))                ? 'S' : 's');
         flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_SEARCH))                ? '!' : ' ');
+        flagdbg.append("> disable2<");
         flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS))       ? 'Q' : 'q');
         flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_QUICK_SETTINGS))       ? '!' : ' ');
+        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS))         ? 'I' : 'i');
+        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_SYSTEM_ICONS))         ? '!' : ' ');
+        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))   ? 'N' : 'n');
+        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))   ? '!' : ' ');
         flagdbg.append('>');
         Log.d(TAG, flagdbg.toString());
 
@@ -2651,6 +2566,13 @@
         if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
             updateQsExpansionEnabled();
         }
+
+        if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+            updateQsExpansionEnabled();
+            if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+                animateCollapsePanels();
+            }
+        }
     }
 
     /**
@@ -2738,11 +2660,8 @@
                 // make sure that the window stays small for one frame until the touchableRegion is set.
                 mNotificationPanel.requestLayout();
                 mStatusBarWindowManager.setForceWindowCollapsed(true);
-                mNotificationPanel.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mStatusBarWindowManager.setForceWindowCollapsed(false);
-                    }
+                mNotificationPanel.post(() -> {
+                    mStatusBarWindowManager.setForceWindowCollapsed(false);
                 });
             }
         } else {
@@ -2754,15 +2673,12 @@
                 // we need to keep the panel open artificially, let's wait until the animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mStackScroller.runAfterAnimationFinished(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!mHeadsUpManager.hasPinnedHeadsUp()) {
-                            mStatusBarWindowManager.setHeadsUpShowing(false);
-                            mHeadsUpManager.setHeadsUpGoingAway(false);
-                        }
-                        removeRemoteInputEntriesKeptUntilCollapsed();
+                mStackScroller.runAfterAnimationFinished(() -> {
+                    if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+                        mStatusBarWindowManager.setHeadsUpShowing(false);
+                        mHeadsUpManager.setHeadsUpGoingAway(false);
                     }
+                    removeRemoteInputEntriesKeptUntilCollapsed();
                 });
             }
         }
@@ -3013,7 +2929,9 @@
     }
 
     boolean panelsEnabled() {
-        return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS;
+        return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0
+                && (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
+                && !ONLY_CORE_APPS;
     }
 
     void makeExpandedVisible(boolean force) {
@@ -3029,7 +2947,6 @@
         mStatusBarWindowManager.setPanelVisible(true);
 
         visibilityChanged(true);
-        mWaitingForKeyguardExit = false;
         recomputeDisableFlags(!force /* animate */);
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
@@ -3038,23 +2955,15 @@
         animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
-    private final Runnable mAnimateCollapsePanels = new Runnable() {
-        @Override
-        public void run() {
-            animateCollapsePanels();
-        }
-    };
+    private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels;
 
     public void postAnimateCollapsePanels() {
         mHandler.post(mAnimateCollapsePanels);
     }
 
     public void postAnimateForceCollapsePanels() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
-            }
+        mHandler.post(() -> {
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
         });
     }
 
@@ -3104,6 +3013,9 @@
             }
         }
 
+        // TODO(b/62444020): remove when this bug is fixed
+        Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): "
+                + mNotificationPanel.canPanelBeCollapsed());
         if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) {
             // release focus immediately to kick off focus change transition
             mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -3209,7 +3121,7 @@
 
         if (SPEW) {
             Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
-                + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking);
+                    + mDisabled1 + " mDisabled2=" + mDisabled2);
         } else if (CHATTY) {
             if (event.getAction() != MotionEvent.ACTION_MOVE) {
                 Log.d(TAG, String.format(
@@ -3294,10 +3206,8 @@
 
             sbModeChanged = sbMode != -1;
             if (sbModeChanged && sbMode != mStatusBarMode) {
-                if (sbMode != mStatusBarMode) {
-                    mStatusBarMode = sbMode;
-                    checkBarModes();
-                }
+                mStatusBarMode = sbMode;
+                checkBarModes();
                 touchAutoHide();
             }
 
@@ -3321,7 +3231,6 @@
         } else {
             cancelAutohide();
         }
-        touchAutoDim();
     }
 
     protected int computeStatusBarMode(int oldVal, int newVal) {
@@ -3385,12 +3294,7 @@
         }
     }
 
-    private final Runnable mCheckBarModes = new Runnable() {
-        @Override
-        public void run() {
-            checkBarModes();
-        }
-    };
+    private final Runnable mCheckBarModes = this::checkBarModes;
 
     public void setInteracting(int barWindow, boolean interacting) {
         final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
@@ -3404,10 +3308,10 @@
         }
         // manually dismiss the volume panel when interacting with the nav bar
         if (changing && interacting && barWindow == StatusBarManager.WINDOW_NAVIGATION_BAR) {
+            touchAutoDim();
             dismissVolumeDialog();
         }
         checkBarModes();
-        touchAutoDim();
     }
 
     private void dismissVolumeDialog() {
@@ -3449,7 +3353,7 @@
         }
     }
 
-    void checkUserAutohide(View v, MotionEvent event) {
+    void checkUserAutohide(MotionEvent event) {
         if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0  // a transient bar is revealed
                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
                 && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
@@ -3516,9 +3420,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mQueueLock) {
             pw.println("Current Status Bar state:");
-            pw.println("  mExpandedVisible=" + mExpandedVisible
-                    + ", mTrackingPosition=" + mTrackingPosition);
-            pw.println("  mTracking=" + mTracking);
+            pw.println("  mExpandedVisible=" + mExpandedVisible);
             pw.println("  mDisplayMetrics=" + mDisplayMetrics);
             pw.println("  mStackScroller: " + viewInfo(mStackScroller));
             pw.println("  mStackScroller: " + viewInfo(mStackScroller)
@@ -3606,16 +3508,12 @@
             if (false) {
                 pw.println("see the logcat for a dump of the views we have created.");
                 // must happen on ui thread
-                mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mStatusBarView.getLocationOnScreen(mAbsPos);
-                            Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
-                                    + ") " + mStatusBarView.getWidth() + "x"
-                                    + getStatusBarHeight());
-                            mStatusBarView.debug();
-                        }
-                    });
+                mHandler.post(() -> {
+                    mStatusBarView.getLocationOnScreen(mAbsPos);
+                    Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] +
+                            ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight());
+                    mStatusBarView.debug();
+                });
             }
         }
 
@@ -3695,49 +3593,43 @@
 
         final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
                 mContext, intent, mCurrentUserId);
-        Runnable runnable = new Runnable() {
-            @Override
-            public void run() {
-                mAssistManager.hideAssist();
-                intent.setFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                int result = ActivityManager.START_CANCELED;
-                ActivityOptions options = new ActivityOptions(getActivityOptions());
-                options.setDisallowEnterPictureInPictureWhileLaunching(
-                        disallowEnterPictureInPictureWhileLaunching);
-                if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
-                    // Normally an activity will set it's requested rotation
-                    // animation on its window. However when launching an activity
-                    // causes the orientation to change this is too late. In these cases
-                    // the default animation is used. This doesn't look good for
-                    // the camera (as it rotates the camera contents out of sync
-                    // with physical reality). So, we ask the WindowManager to
-                    // force the crossfade animation if an orientation change
-                    // happens to occur during the launch.
-                    options.setRotationAnimationHint(
-                            WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
-                }
-                try {
-                    result = ActivityManager.getService().startActivityAsUser(
-                            null, mContext.getBasePackageName(),
-                            intent,
-                            intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                            null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
-                            options.toBundle(), UserHandle.CURRENT.getIdentifier());
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Unable to start activity", e);
-                }
-                if (callback != null) {
-                    callback.onActivityStarted(result);
-                }
+        Runnable runnable = () -> {
+            mAssistManager.hideAssist();
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            int result = ActivityManager.START_CANCELED;
+            ActivityOptions options = new ActivityOptions(getActivityOptions());
+            options.setDisallowEnterPictureInPictureWhileLaunching(
+                    disallowEnterPictureInPictureWhileLaunching);
+            if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
+                // Normally an activity will set it's requested rotation
+                // animation on its window. However when launching an activity
+                // causes the orientation to change this is too late. In these cases
+                // the default animation is used. This doesn't look good for
+                // the camera (as it rotates the camera contents out of sync
+                // with physical reality). So, we ask the WindowManager to
+                // force the crossfade animation if an orientation change
+                // happens to occur during the launch.
+                options.setRotationAnimationHint(
+                        WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
+            }
+            try {
+                result = ActivityManager.getService().startActivityAsUser(
+                        null, mContext.getBasePackageName(),
+                        intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                        null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+                        options.toBundle(), UserHandle.CURRENT.getIdentifier());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Unable to start activity", e);
+            }
+            if (callback != null) {
+                callback.onActivityStarted(result);
             }
         };
-        Runnable cancelRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (callback != null) {
-                    callback.onActivityStarted(ActivityManager.START_CANCELED);
-                }
+        Runnable cancelRunnable = () -> {
+            if (callback != null) {
+                callback.onActivityStarted(ActivityManager.START_CANCELED);
             }
         };
         executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
@@ -3782,7 +3674,7 @@
         }, cancelAction, afterKeyguardGone);
     }
 
-    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3811,7 +3703,7 @@
         }
     };
 
-    private BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3972,7 +3864,6 @@
      * The LEDs are turned off when the notification panel is shown, even just a little bit.
      * See also StatusBar.setPanelExpanded for another place where we attempt to do this.
      */
-    // Old BaseStatusBar.handleVisibileToUserChanged
     private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
         try {
             if (visibleToUser) {
@@ -3997,8 +3888,8 @@
         // Report all notifications as invisible and turn down the
         // reporter.
         if (!mCurrentlyVisibleNotifications.isEmpty()) {
-            logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(),
-                    mCurrentlyVisibleNotifications);
+            logNotificationVisibilityChanges(
+                    Collections.emptyList(), mCurrentlyVisibleNotifications);
             recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
         }
         mHandler.removeCallbacks(mVisibilityReporter);
@@ -4105,7 +3996,7 @@
         vib.vibrate(250, VIBRATION_ATTRIBUTES);
     }
 
-    Runnable mStartTracing = new Runnable() {
+    final Runnable mStartTracing = new Runnable() {
         @Override
         public void run() {
             vibrate();
@@ -4116,13 +4007,10 @@
         }
     };
 
-    Runnable mStopTracing = new Runnable() {
-        @Override
-        public void run() {
-            android.os.Debug.stopMethodTracing();
-            Log.d(TAG, "stopTracing");
-            vibrate();
-        }
+    final Runnable mStopTracing = () -> {
+        android.os.Debug.stopMethodTracing();
+        Log.d(TAG, "stopTracing");
+        vibrate();
     };
 
     @Override
@@ -4149,40 +4037,6 @@
         startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
     }
 
-    private static class FastColorDrawable extends Drawable {
-        private final int mColor;
-
-        public FastColorDrawable(int color) {
-            mColor = 0xff000000 | color;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.drawColor(mColor, PorterDuff.Mode.SRC);
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.OPAQUE;
-        }
-
-        @Override
-        public void setBounds(int left, int top, int right, int bottom) {
-        }
-
-        @Override
-        public void setBounds(Rect bounds) {
-        }
-    }
-
     public void destroy() {
         // Begin old BaseStatusBar.destroy().
         mContext.unregisterReceiver(mBaseBroadcastReceiver);
@@ -4400,31 +4254,23 @@
             Runnable endRunnable) {
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         mLaunchTransitionEndRunnable = endRunnable;
-        Runnable hideRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mLaunchTransitionFadingAway = true;
-                if (beforeFading != null) {
-                    beforeFading.run();
-                }
-                mScrimController.forceHideScrims(true /* hide */, false /* animated */);
-                updateMediaMetaData(false, true);
-                mNotificationPanel.setAlpha(1);
-                mStackScroller.setParentNotFullyVisible(true);
-                mNotificationPanel.animate()
-                        .alpha(0)
-                        .setStartDelay(FADE_KEYGUARD_START_DELAY)
-                        .setDuration(FADE_KEYGUARD_DURATION)
-                        .withLayer()
-                        .withEndAction(new Runnable() {
-                            @Override
-                            public void run() {
-                                onLaunchTransitionFadingEnded();
-                            }
-                        });
-                mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
-                        LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
+        Runnable hideRunnable = () -> {
+            mLaunchTransitionFadingAway = true;
+            if (beforeFading != null) {
+                beforeFading.run();
             }
+            mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+            updateMediaMetaData(false, true);
+            mNotificationPanel.setAlpha(1);
+            mStackScroller.setParentNotFullyVisible(true);
+            mNotificationPanel.animate()
+                    .alpha(0)
+                    .setStartDelay(FADE_KEYGUARD_START_DELAY)
+                    .setDuration(FADE_KEYGUARD_DURATION)
+                    .withLayer()
+                    .withEndAction(this::onLaunchTransitionFadingEnded);
+            mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
+                    LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
         };
         if (mNotificationPanel.isLaunchTransitionRunning()) {
             mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
@@ -4553,7 +4399,6 @@
 
         // Treat Keyguard exit animation as an app transition to achieve nice transition for status
         // bar.
-        mKeyguardGoingAway = true;
         mKeyguardMonitor.notifyKeyguardGoingAway(true);
         mCommandQueue.appTransitionPending(true);
     }
@@ -4562,14 +4407,13 @@
      * Notifies the status bar the Keyguard is fading away with the specified timings.
      *
      * @param startTime the start time of the animations in uptime millis
-     * @param delay the precalculated animation delay in miliseconds
+     * @param delay the precalculated animation delay in milliseconds
      * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
     public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) {
         mKeyguardFadingAway = true;
         mKeyguardFadingAwayDelay = delay;
         mKeyguardFadingAwayDuration = fadeoutDuration;
-        mWaitingForKeyguardExit = false;
         mCommandQueue.appTransitionStarting(startTime + fadeoutDuration
                         - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
                 LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
@@ -4589,14 +4433,9 @@
      */
     public void finishKeyguardFadingAway() {
         mKeyguardFadingAway = false;
-        mKeyguardGoingAway = false;
         mKeyguardMonitor.notifyKeyguardDoneFading();
     }
 
-    public void stopWaitingForKeyguardExit() {
-        mWaitingForKeyguardExit = false;
-    }
-
     private void updatePublicMode() {
         final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing();
         final boolean devicePublic = showingKeyguard
@@ -4810,7 +4649,6 @@
     }
 
     protected void showBouncer() {
-        mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing();
         mStatusBarKeyguardViewManager.dismiss();
     }
 
@@ -4827,7 +4665,7 @@
 
     @Override
     public void onActivated(ActivatableNotificationView view) {
-        onActivated((View)view);
+        onActivated((View) view);
         mStackScroller.setActivatedChild(view);
     }
 
@@ -5022,6 +4860,10 @@
      * @param expandView The view to expand after going to the shade.
      */
     public void goToLockedShade(View expandView) {
+        if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+            return;
+        }
+
         int userId = mCurrentUserId;
         ExpandableNotificationRow row = null;
         if (expandView instanceof ExpandableNotificationRow) {
@@ -5129,51 +4971,41 @@
         updateNotifications();
         if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) {
             // Expand notification panel and the notification row, then click on remote input view
-            final Runnable clickPendingViewRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
-                    if (pendingWorkRemoteInputView == null) {
+            final Runnable clickPendingViewRunnable = () -> {
+                final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
+                if (pendingWorkRemoteInputView == null) {
+                    return;
+                }
+
+                // Climb up the hierarchy until we get to the container for this row.
+                ViewParent p = pendingWorkRemoteInputView.getParent();
+                while (!(p instanceof ExpandableNotificationRow)) {
+                    if (p == null) {
                         return;
                     }
+                    p = p.getParent();
+                }
 
-                    // Climb up the hierarchy until we get to the container for this row.
-                    ViewParent p = pendingWorkRemoteInputView.getParent();
-                    while (!(p instanceof ExpandableNotificationRow)) {
-                        if (p == null) {
-                            return;
+                final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
+                ViewParent viewParent = row.getParent();
+                if (viewParent instanceof NotificationStackScrollLayout) {
+                    final NotificationStackScrollLayout scrollLayout =
+                            (NotificationStackScrollLayout) viewParent;
+                    row.makeActionsVisibile();
+                    row.post(() -> {
+                        final Runnable finishScrollingCallback = () -> {
+                            mPendingWorkRemoteInputView.callOnClick();
+                            mPendingWorkRemoteInputView = null;
+                            scrollLayout.setFinishScrollingCallback(null);
+                        };
+                        if (scrollLayout.scrollTo(row)) {
+                            // It scrolls! So call it when it's finished.
+                            scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
+                        } else {
+                            // It does not scroll, so call it now!
+                            finishScrollingCallback.run();
                         }
-                        p = p.getParent();
-                    }
-
-                    final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
-                    ViewParent viewParent = row.getParent();
-                    if (viewParent instanceof NotificationStackScrollLayout) {
-                        final NotificationStackScrollLayout scrollLayout =
-                                (NotificationStackScrollLayout) viewParent;
-                        row.makeActionsVisibile();
-                        row.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                final Runnable finishScrollingCallback = new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        mPendingWorkRemoteInputView.callOnClick();
-                                        mPendingWorkRemoteInputView = null;
-                                        scrollLayout.setFinishScrollingCallback(null);
-                                    }
-                                };
-                                if (scrollLayout.scrollTo(row)) {
-                                    // It scrolls! So call it when it's finished.
-                                    scrollLayout.setFinishScrollingCallback(
-                                            finishScrollingCallback);
-                                } else {
-                                    // It does not scroll, so call it now!
-                                    finishScrollingCallback.run();
-                                }
-                            }
-                        });
-                    }
+                    });
                 }
             };
             mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
@@ -5236,7 +5068,7 @@
         }
     }
 
-    WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+    final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
         public void onFinishedGoingToSleep() {
             mNotificationPanel.onAffordanceLaunchEnded();
@@ -5259,12 +5091,7 @@
 
                 // This gets executed before we will show Keyguard, so post it in order that the state
                 // is correct.
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onCameraLaunchGestureDetected(mLastCameraLaunchSource);
-                    }
-                });
+                mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource));
             }
             updateIsKeyguard();
         }
@@ -5290,7 +5117,7 @@
         }
     };
 
-    ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+    final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn() {
             mFalsingManager.onScreenTurningOn();
@@ -5487,7 +5314,7 @@
     }
 
     private final class DozeServiceHost implements DozeHost {
-        private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+        private final ArrayList<Callback> mCallbacks = new ArrayList<>();
         private boolean mAnimateWakeup;
         private boolean mIgnoreTouchWhilePulsing;
 
@@ -5700,7 +5527,7 @@
     protected NotificationData mNotificationData;
     protected NotificationStackScrollLayout mStackScroller;
 
-    protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+    protected final NotificationGroupManager mGroupManager = new NotificationGroupManager();
 
     protected RemoteInputController mRemoteInputController;
 
@@ -5710,34 +5537,30 @@
     private AboveShelfObserver mAboveShelfObserver;
 
     // handling reordering
-    protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
+    protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
 
     protected int mCurrentUserId = 0;
-    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
 
-    protected int mLayoutDirection = -1; // invalid
     protected AccessibilityManager mAccessibilityManager;
 
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
-    protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
-    protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
+    protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
+    protected final ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
 
     /**
      * Notifications with keys in this set are not actually around anymore. We kept them around
      * when they were canceled in response to a remote input interaction. This allows us to show
      * what you replied and allows you to continue typing into it.
      */
-    protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+    protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
 
-    private Locale mLocale;
-
     protected boolean mUseHeadsUp = false;
-    protected boolean mHeadsUpTicker = false;
     protected boolean mDisableNotificationAlerts = false;
 
     protected DevicePolicyManager mDevicePolicyManager;
@@ -5772,13 +5595,11 @@
     private NotificationGuts mNotificationGutsExposed;
     private MenuItem mGutsMenuItem;
 
-    private KeyboardShortcuts mKeyboardShortcuts;
-
     protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
-    private NotificationClicker mNotificationClicker = new NotificationClicker();
+    private final NotificationClicker mNotificationClicker = new NotificationClicker();
 
     protected AssistManager mAssistManager;
 
@@ -5838,15 +5659,14 @@
         }
     };
 
-    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+    private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
 
         @Override
         public boolean onClickHandler(
                 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
             wakeUpIfDozing(SystemClock.uptimeMillis(), view);
 
-
-            if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+            if (handleRemoteInput(view, pendingIntent)) {
                 return true;
             }
 
@@ -5864,33 +5684,29 @@
             }
             final boolean isActivity = pendingIntent.isActivity();
             if (isActivity) {
-                final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
                 final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
                         mContext, pendingIntent.getIntent(), mCurrentUserId);
-                dismissKeyguardThenExecute(new OnDismissAction() {
-                    @Override
-                    public boolean onDismiss() {
-                        try {
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-
-                        boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
-
-                        // close the shade if it was open
-                        if (handled && !mNotificationPanel.isFullyCollapsed()) {
-                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                    true /* force */);
-                            visibilityChanged(false);
-                            mAssistManager.hideAssist();
-
-                            // Wait for activity start.
-                            return true;
-                        } else {
-                            return false;
-                        }
-
+                dismissKeyguardThenExecute(() -> {
+                    try {
+                        ActivityManager.getService().resumeAppSwitches();
+                    } catch (RemoteException e) {
                     }
+
+                    boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
+
+                    // close the shade if it was open
+                    if (handled && !mNotificationPanel.isFullyCollapsed()) {
+                        animateCollapsePanels(
+                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+                        visibilityChanged(false);
+                        mAssistManager.hideAssist();
+
+                        // Wait for activity start.
+                        return true;
+                    } else {
+                        return false;
+                    }
+
                 }, afterKeyguardGone);
                 return true;
             } else {
@@ -5932,10 +5748,15 @@
         private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
                 Intent fillInIntent) {
             return super.onClickHandler(view, pendingIntent, fillInIntent,
-                    StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
         }
 
-        private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+        private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
+            if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+                // Skip remote input as doing so will expand the notification shade.
+                return true;
+            }
+
             Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
             RemoteInput[] inputs = null;
             if (tag instanceof RemoteInput[]) {
@@ -6050,7 +5871,7 @@
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 updateCurrentProfilesCache();
-                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+                Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
 
                 updateLockscreenNotificationSetting();
 
@@ -6073,8 +5894,7 @@
                         Toast toast = Toast.makeText(mContext,
                                 R.string.managed_profile_foreground_toast,
                                 Toast.LENGTH_SHORT);
-                        TextView text = (TextView) toast.getView().findViewById(
-                                android.R.id.message);
+                        TextView text = toast.getView().findViewById(android.R.id.message);
                         text.setCompoundDrawablesRelativeWithIntrinsicBounds(
                                 R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
                         int paddingPx = mContext.getResources().getDimensionPixelSize(
@@ -6150,15 +5970,12 @@
                 return;
             }
             final RankingMap currentRanking = getCurrentRanking();
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    for (StatusBarNotification sbn : notifications) {
-                        try {
-                            addNotification(sbn, currentRanking);
-                        } catch (InflationException e) {
-                            handleInflationException(sbn, e);
-                        }
+            mHandler.post(() -> {
+                for (StatusBarNotification sbn : notifications) {
+                    try {
+                        addNotification(sbn, currentRanking);
+                    } catch (InflationException e) {
+                        handleInflationException(sbn, e);
                     }
                 }
             });
@@ -6169,40 +5986,37 @@
                 final RankingMap rankingMap) {
             if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
             if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        processForRemoteInput(sbn.getNotification());
-                        String key = sbn.getKey();
-                        mKeysKeptForRemoteInput.remove(key);
-                        boolean isUpdate = mNotificationData.get(key) != null;
-                        // In case we don't allow child notifications, we ignore children of
-                        // notifications that have a summary, since we're not going to show them
-                        // anyway. This is true also when the summary is canceled,
-                        // because children are automatically canceled by NoMan in that case.
-                        if (!ENABLE_CHILD_NOTIFICATIONS
+                mHandler.post(() -> {
+                    processForRemoteInput(sbn.getNotification());
+                    String key = sbn.getKey();
+                    mKeysKeptForRemoteInput.remove(key);
+                    boolean isUpdate = mNotificationData.get(key) != null;
+                    // In case we don't allow child notifications, we ignore children of
+                    // notifications that have a summary, since we're not going to show them
+                    // anyway. This is true also when the summary is canceled,
+                    // because children are automatically canceled by NoMan in that case.
+                    if (!ENABLE_CHILD_NOTIFICATIONS
                             && mGroupManager.isChildInGroupWithSummary(sbn)) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
-                            }
+                        if (DEBUG) {
+                            Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+                        }
 
-                            // Remove existing notification to avoid stale data.
-                            if (isUpdate) {
-                                removeNotification(key, rankingMap);
-                            } else {
-                                mNotificationData.updateRanking(rankingMap);
-                            }
-                            return;
+                        // Remove existing notification to avoid stale data.
+                        if (isUpdate) {
+                            removeNotification(key, rankingMap);
+                        } else {
+                            mNotificationData.updateRanking(rankingMap);
                         }
-                        try {
-                            if (isUpdate) {
-                                updateNotification(sbn, rankingMap);
-                            } else {
-                                addNotification(sbn, rankingMap);
-                            }
-                        } catch (InflationException e) {
-                            handleInflationException(sbn, e);
+                        return;
+                    }
+                    try {
+                        if (isUpdate) {
+                            updateNotification(sbn, rankingMap);
+                        } else {
+                            addNotification(sbn, rankingMap);
                         }
+                    } catch (InflationException e) {
+                        handleInflationException(sbn, e);
                     }
                 });
             }
@@ -6250,7 +6064,7 @@
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 return;
             }
-            Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+            Log.d(TAG, "disabling lockscreen notifications and alerting the user");
             // disable lockscreen notifications until user acts on the banner.
             Settings.Secure.putInt(mContext.getContentResolver(),
                     Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
@@ -6291,11 +6105,10 @@
 
     @Override  // NotificationData.Environment
     public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
-        final int thisUserId = mCurrentUserId;
         final int notificationUserId = n.getUserId();
         if (DEBUG && MULTIUSER_DEBUG) {
-            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
-                    n, thisUserId, notificationUserId));
+            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
+                    mCurrentUserId, notificationUserId));
         }
         return isCurrentProfile(notificationUserId);
     }
@@ -6343,21 +6156,15 @@
     }
 
     private void startNotificationGutsIntent(final Intent intent, final int appUid) {
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                AsyncTask.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        TaskStackBuilder.create(mContext)
-                                .addNextIntentWithParentStack(intent)
-                                .startActivities(getActivityOptions(),
-                                        new UserHandle(UserHandle.getUserId(appUid)));
-                    }
-                });
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-                return true;
-            }
+        dismissKeyguardThenExecute(() -> {
+            AsyncTask.execute(() -> {
+                TaskStackBuilder.create(mContext)
+                        .addNextIntentWithParentStack(intent)
+                        .startActivities(getActivityOptions(),
+                                new UserHandle(UserHandle.getUserId(appUid)));
+            });
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+            return true;
         }, false /* afterKeyguardGone */);
     }
 
@@ -6428,7 +6235,7 @@
                 startNotificationGutsIntent(intent, sbn.getUid());
             };
             final View.OnClickListener onDoneClick = (View v) -> {
-                saveAndCloseNotificationMenu(info, row, guts, v);
+                saveAndCloseNotificationMenu(row, guts, v);
             };
             final NotificationInfo.CheckSaveListener checkSaveListener =
                     (Runnable saveImportance) -> {
@@ -6445,7 +6252,7 @@
                 }
             };
 
-            ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>();
+            ArraySet<NotificationChannel> channels = new ArraySet<>();
             channels.add(row.getEntry().channel);
             if (row.isSummaryWithChildren()) {
                 // If this is a summary, then add in the children notification channels for the
@@ -6473,7 +6280,7 @@
         }
     }
 
-    private void saveAndCloseNotificationMenu(NotificationInfo info,
+    private void saveAndCloseNotificationMenu(
             ExpandableNotificationRow row, NotificationGuts guts, View done) {
         guts.resetFalsingCheck();
         int[] rowLocation = new int[2];
@@ -6642,13 +6449,6 @@
         updateHideIconsForBouncer(true /* animate */);
     }
 
-    protected void sendCloseSystemWindows(String reason) {
-        try {
-            ActivityManager.getService().closeSystemDialogs(reason);
-        } catch (RemoteException e) {
-        }
-    }
-
     protected void toggleKeyboardShortcuts(int deviceId) {
         KeyboardShortcuts.toggle(mContext, deviceId);
     }
@@ -6750,18 +6550,6 @@
         return isLockscreenPublicMode(userId);
     }
 
-    public void onNotificationClear(StatusBarNotification notification) {
-        try {
-            mBarService.onNotificationClear(
-                    notification.getPackageName(),
-                    notification.getTag(),
-                    notification.getId(),
-                    notification.getUserId());
-        } catch (android.os.RemoteException ex) {
-            // oh well
-        }
-    }
-
     /**
      * Called when the notification panel layouts
      */
@@ -6919,49 +6707,42 @@
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         if (!isDeviceProvisioned()) return;
 
-        final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
         final boolean afterKeyguardGone = intent.isActivity()
                 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                 mCurrentUserId);
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                new Thread() {
-                    @Override
-                    public void run() {
-                        try {
-                            // The intent we are sending is for the application, which
-                            // won't have permission to immediately start an activity after
-                            // the user switches to home.  We know it is safe to do at this
-                            // point, so make sure new activity switches are now allowed.
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-                        try {
-                            intent.send(null, 0, null, null, null, null, getActivityOptions());
-                        } catch (PendingIntent.CanceledException e) {
-                            // the stack trace isn't very helpful here.
-                            // Just log the exception message.
-                            Log.w(TAG, "Sending intent failed: " + e);
-
-                            // TODO: Dismiss Keyguard.
-                        }
-                        if (intent.isActivity()) {
-                            mAssistManager.hideAssist();
-                        }
-                    }
-                }.start();
-
-                if (!mNotificationPanel.isFullyCollapsed()) {
-                    // close the shade if it was open
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */, true /* delayed */);
-                    visibilityChanged(false);
-
-                    return true;
-                } else {
-                    return false;
+        dismissKeyguardThenExecute(() -> {
+            new Thread(() -> {
+                try {
+                    // The intent we are sending is for the application, which
+                    // won't have permission to immediately start an activity after
+                    // the user switches to home.  We know it is safe to do at this
+                    // point, so make sure new activity switches are now allowed.
+                    ActivityManager.getService().resumeAppSwitches();
+                } catch (RemoteException e) {
                 }
+                try {
+                    intent.send(null, 0, null, null, null, null, getActivityOptions());
+                } catch (PendingIntent.CanceledException e) {
+                    // the stack trace isn't very helpful here.
+                    // Just log the exception message.
+                    Log.w(TAG, "Sending intent failed: " + e);
+
+                    // TODO: Dismiss Keyguard.
+                }
+                if (intent.isActivity()) {
+                    mAssistManager.hideAssist();
+                }
+            }).start();
+
+            if (!mNotificationPanel.isFullyCollapsed()) {
+                // close the shade if it was open
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                        true /* delayed */);
+                visibilityChanged(false);
+
+                return true;
+            } else {
+                return false;
             }
         }, afterKeyguardGone);
     }
@@ -6999,130 +6780,110 @@
 
             // Mark notification for one frame.
             row.setJustClicked(true);
-            DejankUtils.postAfterTraversal(new Runnable() {
-                @Override
-                public void run() {
-                    row.setJustClicked(false);
-                }
-            });
+            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
 
             final boolean afterKeyguardGone = intent.isActivity()
                     && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                             mCurrentUserId);
-            dismissKeyguardThenExecute(new OnDismissAction() {
-                @Override
-                public boolean onDismiss() {
-                    if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
-                        // Release the HUN notification to the shade.
+            dismissKeyguardThenExecute(() -> {
+                if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+                    // Release the HUN notification to the shade.
 
-                        if (isPanelFullyCollapsed()) {
-                            HeadsUpManager.setIsClickedNotification(row, true);
-                        }
-                        //
-                        // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
-                        // become canceled shortly by NoMan, but we can't assume that.
-                        mHeadsUpManager.releaseImmediately(notificationKey);
+                    if (isPanelFullyCollapsed()) {
+                        HeadsUpManager.setIsClickedNotification(row, true);
                     }
-                    StatusBarNotification parentToCancel = null;
-                    if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-                        StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
-                                        .getStatusBarNotification();
-                        if (shouldAutoCancel(summarySbn)) {
-                            parentToCancel = summarySbn;
-                        }
+                    //
+                    // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+                    // become canceled shortly by NoMan, but we can't assume that.
+                    mHeadsUpManager.releaseImmediately(notificationKey);
+                }
+                StatusBarNotification parentToCancel = null;
+                if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+                    StatusBarNotification summarySbn =
+                            mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+                    if (shouldAutoCancel(summarySbn)) {
+                        parentToCancel = summarySbn;
                     }
-                    final StatusBarNotification parentToCancelFinal = parentToCancel;
-                    final Runnable runnable = new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                // The intent we are sending is for the application, which
-                                // won't have permission to immediately start an activity after
-                                // the user switches to home.  We know it is safe to do at this
-                                // point, so make sure new activity switches are now allowed.
-                                ActivityManager.getService().resumeAppSwitches();
-                            } catch (RemoteException e) {
-                            }
-                            if (intent != null) {
-                                // If we are launching a work activity and require to launch
-                                // separate work challenge, we defer the activity action and cancel
-                                // notification until work challenge is unlocked.
-                                if (intent.isActivity()) {
-                                    final int userId = intent.getCreatorUserHandle()
-                                            .getIdentifier();
-                                    if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-                                            && mKeyguardManager.isDeviceLocked(userId)) {
-                                        // TODO(b/28935539): should allow certain activities to
-                                        // bypass work challenge
-                                        if (startWorkChallengeIfNecessary(userId,
-                                                intent.getIntentSender(), notificationKey)) {
-                                            // Show work challenge, do not run PendingIntent and
-                                            // remove notification
-                                            return;
-                                        }
-                                    }
-                                }
-                                try {
-                                    intent.send(null, 0, null, null, null, null,
-                                            getActivityOptions());
-                                } catch (PendingIntent.CanceledException e) {
-                                    // the stack trace isn't very helpful here.
-                                    // Just log the exception message.
-                                    Log.w(TAG, "Sending contentIntent failed: " + e);
-
-                                    // TODO: Dismiss Keyguard.
-                                }
-                                if (intent.isActivity()) {
-                                    mAssistManager.hideAssist();
+                }
+                final StatusBarNotification parentToCancelFinal = parentToCancel;
+                final Runnable runnable = () -> {
+                    try {
+                        // The intent we are sending is for the application, which
+                        // won't have permission to immediately start an activity after
+                        // the user switches to home.  We know it is safe to do at this
+                        // point, so make sure new activity switches are now allowed.
+                        ActivityManager.getService().resumeAppSwitches();
+                    } catch (RemoteException e) {
+                    }
+                    if (intent != null) {
+                        // If we are launching a work activity and require to launch
+                        // separate work challenge, we defer the activity action and cancel
+                        // notification until work challenge is unlocked.
+                        if (intent.isActivity()) {
+                            final int userId = intent.getCreatorUserHandle().getIdentifier();
+                            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+                                    && mKeyguardManager.isDeviceLocked(userId)) {
+                                // TODO(b/28935539): should allow certain activities to
+                                // bypass work challenge
+                                if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+                                        notificationKey)) {
+                                    // Show work challenge, do not run PendingIntent and
+                                    // remove notification
+                                    return;
                                 }
                             }
-
-                            try {
-                                mBarService.onNotificationClick(notificationKey);
-                            } catch (RemoteException ex) {
-                                // system process is dead if we're here.
-                            }
-                            if (parentToCancelFinal != null) {
-                                // We have to post it to the UI thread for synchronization
-                                mHandler.post(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        Runnable removeRunnable = new Runnable() {
-                                            @Override
-                                            public void run() {
-                                                performRemoveNotification(parentToCancelFinal);
-                                            }
-                                        };
-                                        if (isCollapsing()) {
-                                            // To avoid lags we're only performing the remove
-                                            // after the shade was collapsed
-                                            addPostCollapseAction(removeRunnable);
-                                        } else {
-                                            removeRunnable.run();
-                                        }
-                                    }
-                                });
-                            }
                         }
-                    };
+                        try {
+                            intent.send(null, 0, null, null, null, null, getActivityOptions());
+                        } catch (PendingIntent.CanceledException e) {
+                            // the stack trace isn't very helpful here.
+                            // Just log the exception message.
+                            Log.w(TAG, "Sending contentIntent failed: " + e);
 
-                    if (mStatusBarKeyguardViewManager.isShowing()
-                            && mStatusBarKeyguardViewManager.isOccluded()) {
-                        mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-                    } else {
-                        new Thread(runnable).start();
+                            // TODO: Dismiss Keyguard.
+                        }
+                        if (intent.isActivity()) {
+                            mAssistManager.hideAssist();
+                        }
                     }
 
-                    if (!mNotificationPanel.isFullyCollapsed()) {
-                        // close the shade if it was open
-                        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                true /* force */, true /* delayed */);
-                        visibilityChanged(false);
-
-                        return true;
-                    } else {
-                        return false;
+                    try {
+                        mBarService.onNotificationClick(notificationKey);
+                    } catch (RemoteException ex) {
+                        // system process is dead if we're here.
                     }
+                    if (parentToCancelFinal != null) {
+                        // We have to post it to the UI thread for synchronization
+                        mHandler.post(() -> {
+                            Runnable removeRunnable =
+                                    () -> performRemoveNotification(parentToCancelFinal);
+                            if (isCollapsing()) {
+                                // To avoid lags we're only performing the remove
+                                // after the shade was collapsed
+                                addPostCollapseAction(removeRunnable);
+                            } else {
+                                removeRunnable.run();
+                            }
+                        });
+                    }
+                };
+
+                if (mStatusBarKeyguardViewManager.isShowing()
+                        && mStatusBarKeyguardViewManager.isOccluded()) {
+                    mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+                } else {
+                    new Thread(runnable).start();
+                }
+
+                if (!mNotificationPanel.isFullyCollapsed()) {
+                    // close the shade if it was open
+                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                            true /* delayed */);
+                    visibilityChanged(false);
+
+                    return true;
+                } else {
+                    return false;
                 }
             }, afterKeyguardGone);
         }
@@ -7149,10 +6910,10 @@
     }
 
     protected Bundle getActivityOptions() {
-        // Anything launched from the notification shade should always go into the
-        // fullscreen stack.
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+        // Anything launched from the notification shade should always go into the secondary
+        // split-screen windowing mode.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
         return options.toBundle();
     }
 
diff --git a/com/android/systemui/statusbar/phone/StatusBarIconController.java b/com/android/systemui/statusbar/phone/StatusBarIconController.java
index c240765..bcda60e 100644
--- a/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -14,10 +14,10 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.annotation.ColorInt;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -29,11 +29,11 @@
 import android.widget.LinearLayout.LayoutParams;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 public interface StatusBarIconController {
 
@@ -149,6 +149,14 @@
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
+
+            DisableStateTracker tracker =
+                    new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS);
+            mGroup.addOnAttachStateChangeListener(tracker);
+            if (mGroup.isAttachedToWindow()) {
+                // In case we miss the first onAttachedToWindow event
+                tracker.onViewAttachedToWindow(mGroup);
+            }
         }
 
         protected void onIconAdded(int index, String slot, boolean blocked,
diff --git a/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 68f8e06..1c3ee75 100644
--- a/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -50,21 +49,17 @@
 public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
         ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
 
-    private final DarkIconDispatcher mDarkIconDispatcher;
+    private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
+    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
+    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
 
     private Context mContext;
     private DemoStatusIcons mDemoStatusIcons;
 
-    private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
-
-    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
-    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
-
     public StatusBarIconControllerImpl(Context context) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
         Dependency.get(ConfigurationController.class).addCallback(this);
-        mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
         mContext = context;
 
         loadDimens();
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bbce751..09828dc 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -225,7 +225,6 @@
         if (mShowing) {
             if (mOccluded && !mDozing) {
                 mStatusBar.hideKeyguard();
-                mStatusBar.stopWaitingForKeyguardExit();
                 if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
                     hideBouncer(false /* destroyView */);
                 }
diff --git a/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index c0a6837..0d21c4e 100644
--- a/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.wifi.WifiManager.ActionListener;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -59,13 +58,19 @@
 
     private int mCurrentUser;
 
-    public AccessPointControllerImpl(Context context, Looper bgLooper) {
+    public AccessPointControllerImpl(Context context) {
         mContext = context;
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mWifiTracker = new WifiTracker(context, this, bgLooper, false, true);
+        mWifiTracker = new WifiTracker(context, this, false, true);
         mCurrentUser = ActivityManager.getCurrentUser();
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        mWifiTracker.onDestroy();
+    }
+
     public boolean canConfigWifi() {
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
                 new UserHandle(mCurrentUser));
@@ -81,7 +86,7 @@
         if (DEBUG) Log.d(TAG, "addCallback " + callback);
         mCallbacks.add(callback);
         if (mCallbacks.size() == 1) {
-            mWifiTracker.startTracking();
+            mWifiTracker.onStart();
         }
     }
 
@@ -91,7 +96,7 @@
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
         mCallbacks.remove(callback);
         if (mCallbacks.isEmpty()) {
-            mWifiTracker.stopTracking();
+            mWifiTracker.onStop();
         }
     }
 
diff --git a/com/android/systemui/statusbar/policy/CallbackHandler.java b/com/android/systemui/statusbar/policy/CallbackHandler.java
index a456786..5159e8d 100644
--- a/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -71,7 +71,7 @@
                 break;
             case MSG_NO_SIM_VISIBLE_CHANGED:
                 for (SignalCallback signalCluster : mSignalCallbacks) {
-                    signalCluster.setNoSims(msg.arg1 != 0);
+                    signalCluster.setNoSims(msg.arg1 != 0, msg.arg2 != 0);
                 }
                 break;
             case MSG_ETHERNET_CHANGED:
@@ -144,8 +144,8 @@
     }
 
     @Override
-    public void setNoSims(boolean show) {
-        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+    public void setNoSims(boolean show, boolean simDetected) {
+        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
     }
 
     @Override
diff --git a/com/android/systemui/statusbar/policy/NetworkController.java b/com/android/systemui/statusbar/policy/NetworkController.java
index 2771011..9eee906 100644
--- a/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/com/android/systemui/statusbar/policy/NetworkController.java
@@ -52,7 +52,7 @@
                 int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
                 String description, boolean isWide, int subId, boolean roaming) {}
         default void setSubs(List<SubscriptionInfo> subs) {}
-        default void setNoSims(boolean show) {}
+        default void setNoSims(boolean show, boolean simDetected) {}
 
         default void setEthernetIndicators(IconState icon) {}
 
diff --git a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c217bda..d24e51c 100644
--- a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -58,10 +58,8 @@
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 
@@ -116,7 +114,7 @@
 
     // States that don't belong to a subcontroller.
     private boolean mAirplaneMode = false;
-    private boolean mHasNoSims;
+    private boolean mHasNoSubs;
     private Locale mLocale = null;
     // This list holds our ordering.
     private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
@@ -140,6 +138,7 @@
     @VisibleForTesting
     ServiceState mLastServiceState;
     private boolean mUserSetup;
+    private boolean mSimDetected;
 
     /**
      * Construct this controller object and register for updates.
@@ -151,7 +150,7 @@
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
                 SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
                 new CallbackHandler(),
-                new AccessPointControllerImpl(context, bgLooper),
+                new AccessPointControllerImpl(context),
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController);
@@ -363,7 +362,7 @@
         cb.setSubs(mCurrentSubscriptions);
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        cb.setNoSims(mHasNoSims);
+        cb.setNoSims(mHasNoSubs, mSimDetected);
         mWifiSignalController.notifyListeners(cb);
         mEthernetSignalController.notifyListeners(cb);
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -498,13 +497,27 @@
 
     @VisibleForTesting
     protected void updateNoSims() {
-        boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
-        if (hasNoSims != mHasNoSims) {
-            mHasNoSims = hasNoSims;
-            mCallbackHandler.setNoSims(mHasNoSims);
+        boolean hasNoSubs = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
+        boolean simDetected = hasAnySim();
+        if (hasNoSubs != mHasNoSubs || simDetected != mSimDetected) {
+            mHasNoSubs = hasNoSubs;
+            mSimDetected = simDetected;
+            mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
     }
 
+    private boolean hasAnySim() {
+        int simCount = mPhone.getSimCount();
+        for (int i = 0; i < simCount; i++) {
+            int state = mPhone.getSimState(i);
+            if (state != TelephonyManager.SIM_STATE_ABSENT
+                    && state != TelephonyManager.SIM_STATE_UNKNOWN) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @VisibleForTesting
     void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
         Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
@@ -631,7 +644,7 @@
     private void notifyListeners() {
         mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        mCallbackHandler.setNoSims(mHasNoSims);
+        mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
     }
 
     /**
@@ -804,6 +817,10 @@
                 } else {
                     mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
                 }
+                String ssid = args.getString("ssid");
+                if (ssid != null) {
+                    mDemoWifiState.ssid = ssid;
+                }
                 mDemoWifiState.enabled = show;
                 mWifiSignalController.notifyListeners();
             }
@@ -822,8 +839,8 @@
             }
             String nosim = args.getString("nosim");
             if (nosim != null) {
-                mHasNoSims = nosim.equals("show");
-                mCallbackHandler.setNoSims(mHasNoSims);
+                mHasNoSubs = nosim.equals("show");
+                mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
             }
             String mobile = args.getString("mobile");
             if (mobile != null) {
diff --git a/com/android/systemui/util/Utils.java b/com/android/systemui/util/Utils.java
index f4aebae..eca6127 100644
--- a/com/android/systemui/util/Utils.java
+++ b/com/android/systemui/util/Utils.java
@@ -14,6 +14,11 @@
 
 package com.android.systemui.util;
 
+import android.view.View;
+
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.CommandQueue;
+
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -28,4 +33,52 @@
             c.accept(list.get(i));
         }
     }
+
+    /**
+     * Sets the visibility of an UI element according to the DISABLE_* flags in
+     * {@link android.app.StatusBarManager}.
+     */
+    public static class DisableStateTracker implements CommandQueue.Callbacks,
+            View.OnAttachStateChangeListener {
+        private final int mMask1;
+        private final int mMask2;
+        private View mView;
+        private boolean mDisabled;
+
+        public DisableStateTracker(int disableMask, int disable2Mask) {
+            mMask1 = disableMask;
+            mMask2 = disable2Mask;
+        }
+
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            mView = v;
+            SysUiServiceProvider.getComponent(v.getContext(), CommandQueue.class)
+                    .addCallbacks(this);
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            SysUiServiceProvider.getComponent(mView.getContext(), CommandQueue.class)
+                    .removeCallbacks(this);
+            mView = null;
+        }
+
+        /**
+         * Sets visibility of this {@link View} given the states passed from
+         * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int)}.
+         */
+        @Override
+        public void disable(int state1, int state2, boolean animate) {
+            final boolean disabled = ((state1 & mMask1) != 0) || ((state2 & mMask2) != 0);
+            if (disabled == mDisabled) return;
+            mDisabled = disabled;
+            mView.setVisibility(disabled ? View.GONE : View.VISIBLE);
+        }
+
+        /** @return {@code true} if and only if this {@link View} is currently disabled */
+        public boolean isDisabled() {
+            return mDisabled;
+        }
+    }
 }
diff --git a/com/android/systemui/volume/ZenModePanel.java b/com/android/systemui/volume/ZenModePanel.java
index a3aca6e..7bb987c 100644
--- a/com/android/systemui/volume/ZenModePanel.java
+++ b/com/android/systemui/volume/ZenModePanel.java
@@ -524,18 +524,17 @@
             bindGenericCountdown();
             bindNextAlarm(getTimeUntilNextAlarmCondition());
         } else if (isForever(c)) {
+
             getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
             bindGenericCountdown();
             bindNextAlarm(getTimeUntilNextAlarmCondition());
         } else {
             if (isAlarm(c)) {
                 bindGenericCountdown();
-
                 bindNextAlarm(c);
                 getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true);
             } else if (isCountdown(c)) {
                 bindNextAlarm(getTimeUntilNextAlarmCondition());
-
                 bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
                         COUNTDOWN_CONDITION_INDEX);
                 getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
@@ -568,8 +567,8 @@
         tag = (ConditionTag) alarmContent.getTag();
         boolean showAlarm = tag != null && tag.condition != null;
         mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(
-                showAlarm ? View.VISIBLE : View.GONE);
-        alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
+                showAlarm ? View.VISIBLE : View.INVISIBLE);
+        alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.INVISIBLE);
     }
 
     private Condition forever() {
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 7c9aede..3d5476d 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2012 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,24 +16,55 @@
 
 package com.android.uiautomator.testrunner;
 
-import android.app.Instrumentation;
+import android.content.Context;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.test.InstrumentationTestCase;
+import android.view.inputmethod.InputMethodInfo;
 
-import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
+import com.android.internal.view.IInputMethodManager;
 import com.android.uiautomator.core.UiDevice;
 
+import junit.framework.TestCase;
+
+import java.util.List;
+
 /**
- * UI Automator test case that is executed on the device.
+ * 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
  * @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 InstrumentationTestCase {
+public class UiAutomatorTestCase extends TestCase {
 
+    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
@@ -41,7 +72,7 @@
      * @since API Level 16
      */
     public UiDevice getUiDevice() {
-        return UiDevice.getInstance();
+        return mUiDevice;
     }
 
     /**
@@ -54,43 +85,34 @@
         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;
     }
 
     /**
-     * Initializes this test case.
-     *
-     * @param params Instrumentation arguments.
+     * package private
+     * @param uiDevice
      */
-    void initialize(Bundle params) {
+    void setUiDevice(UiDevice uiDevice) {
+        mUiDevice = uiDevice;
+    }
+
+    /**
+     * package private
+     * @param params
+     */
+    void setParams(Bundle params) {
         mParams = params;
+    }
 
-        // 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()));
+    void setAutomationSupport(IAutomationSupport automationSupport) {
+        mAutomationSupport = automationSupport;
     }
 
     /**
@@ -101,4 +123,28 @@
     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/com/android/webview/nullwebview/NullWebViewFactoryProvider.java b/com/android/webview/nullwebview/NullWebViewFactoryProvider.java
deleted file mode 100644
index ed12446..0000000
--- a/com/android/webview/nullwebview/NullWebViewFactoryProvider.java
+++ /dev/null
@@ -1,85 +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.webview.nullwebview;
-
-import android.content.Context;
-import android.webkit.CookieManager;
-import android.webkit.GeolocationPermissions;
-import android.webkit.ServiceWorkerController;
-import android.webkit.TokenBindingService;
-import android.webkit.WebIconDatabase;
-import android.webkit.WebStorage;
-import android.webkit.WebView;
-import android.webkit.WebViewDatabase;
-import android.webkit.WebViewDelegate;
-import android.webkit.WebViewFactoryProvider;
-import android.webkit.WebViewProvider;
-
-public class NullWebViewFactoryProvider implements WebViewFactoryProvider {
-
-    public static WebViewFactoryProvider create(WebViewDelegate delegate) {
-        return new NullWebViewFactoryProvider(delegate);
-    }
-
-    public NullWebViewFactoryProvider(WebViewDelegate delegate) {
-    }
-
-    @Override
-    public WebViewFactoryProvider.Statics getStatics() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public GeolocationPermissions getGeolocationPermissions() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public CookieManager getCookieManager() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public TokenBindingService getTokenBindingService() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public ServiceWorkerController getServiceWorkerController() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebIconDatabase getWebIconDatabase() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebStorage getWebStorage() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebViewDatabase getWebViewDatabase(Context context) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/foo/bar/ComplexDao.java b/foo/bar/ComplexDao.java
index ca90163..89859e5 100644
--- a/foo/bar/ComplexDao.java
+++ b/foo/bar/ComplexDao.java
@@ -31,6 +31,11 @@
         mDb = db;
     }
 
+    @Transaction
+    public boolean transactionMethod(int i, String s, long l) {
+        return true;
+    }
+
     @Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id")
     abstract public List<FullName> fullNames(int id);
 
diff --git a/foo/bar/MultiPKeyEntity.java b/foo/bar/MultiPKeyEntity.java
index dccb9dd..59570ad 100644
--- a/foo/bar/MultiPKeyEntity.java
+++ b/foo/bar/MultiPKeyEntity.java
@@ -15,9 +15,12 @@
  */
 
 package foo.bar;
+import android.support.annotation.NonNull;
 import android.arch.persistence.room.*;
 @Entity(primaryKeys = {"name", "lastName"})
 public class MultiPKeyEntity {
+    @NonNull
     String name;
+    @NonNull
     String lastName;
 }
diff --git a/java/io/File.java b/java/io/File.java
index 0d2fbb9..98956ca 100644
--- a/java/io/File.java
+++ b/java/io/File.java
@@ -37,6 +37,7 @@
 import java.nio.file.FileSystems;
 import sun.security.action.GetPropertyAction;
 
+// Android-added: Info about UTF-8 usage in filenames.
 /**
  * An abstract representation of file and directory pathnames.
  *
@@ -321,9 +322,11 @@
         if (child == null) {
             throw new NullPointerException();
         }
+        // BEGIN Android-changed: b/25859957, app-compat; don't substitute empty parent.
         if (parent != null && !parent.isEmpty()) {
             this.path = fs.resolve(fs.normalize(parent),
                                    fs.normalize(child));
+        // END Android-changed: b/25859957, app-compat; don't substitute empty parent.
         } else {
             this.path = fs.normalize(child);
         }
@@ -515,6 +518,7 @@
 
     /* -- Path operations -- */
 
+    // Android-changed: Android-specific path information
     /**
      * Tests whether this abstract pathname is absolute.  The definition of
      * absolute pathname is system dependent.  On Android, absolute paths start with
@@ -527,6 +531,7 @@
         return fs.isAbsolute(this);
     }
 
+    // Android-changed: Android-specific path information
     /**
      * Returns the absolute path of this file. An absolute path is a path that starts at a root
      * of the file system. On Android, there is only one root: {@code /}.
@@ -671,6 +676,8 @@
         if (isInvalid()) {
             throw new MalformedURLException("Invalid file path");
         }
+        // Android-changed: Fix for new File("").toURL().
+        // return new URL("file", "", slashify(getAbsolutePath(), isDirectory()));
         return new URL("file", "", slashify(getAbsolutePath(),
                 getAbsoluteFile().isDirectory()));
     }
@@ -804,6 +811,7 @@
             return false;
         }
 
+        // Android-changed: b/25878034 work around SELinux stat64 denial.
         return fs.checkAccess(this, FileSystem.ACCESS_OK);
     }
 
@@ -1029,6 +1037,7 @@
         return fs.delete(this);
     }
 
+    // Android-added: Additional information about Android behaviour.
     /**
      * Requests that the file or directory denoted by this abstract
      * pathname be deleted when the virtual machine terminates.
@@ -1355,6 +1364,7 @@
                 canonFile.mkdir());
     }
 
+    // Android-changed: Replaced generic platform info with Android specific one.
     /**
      * Renames the file denoted by this abstract pathname.
      *
@@ -1441,7 +1451,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * Marks the file or directory named by this abstract pathname so that
      * only read operations are allowed. After invoking this method the file
@@ -1471,7 +1481,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * Sets the owner's or everybody's write permission for this abstract
      * pathname.
@@ -1514,7 +1524,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * A convenience method to set the owner's write permission for this abstract
      * pathname.
@@ -1545,7 +1555,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * Sets the owner's or everybody's read permission for this abstract
      * pathname.
@@ -1591,7 +1601,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * A convenience method to set the owner's read permission for this abstract
      * pathname.
@@ -1625,7 +1635,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * Sets the owner's or everybody's execute permission for this abstract
      * pathname.
@@ -1671,7 +1681,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * A convenience method to set the owner's execute permission for this
      * abstract pathname.
@@ -1705,7 +1715,7 @@
     }
 
     // Android-changed. Removed javadoc comment about special privileges
-    // that doesn't make sense on android
+    // that doesn't make sense on Android.
     /**
      * Tests whether the application can execute the file denoted by this
      * abstract pathname.
@@ -1734,7 +1744,7 @@
 
     /* -- Filesystem interface -- */
 
-
+    // Android-changed: Replaced generic platform info with Android specific one.
     /**
      * Returns the file system roots. On Android and other Unix systems, there is
      * a single root, {@code /}.
@@ -1811,6 +1821,7 @@
         return fs.getSpace(this, FileSystem.SPACE_FREE);
     }
 
+    // Android-added: Replaced generic platform info with Android specific one.
     /**
      * Returns the number of bytes available to this virtual machine on the
      * partition <a href="#partName">named</a> by this abstract pathname.  When
@@ -1858,18 +1869,22 @@
     }
 
     /* -- Temporary files -- */
+
     private static class TempDirectory {
         private TempDirectory() { }
 
         // Android-changed: Don't cache java.io.tmpdir value
-        // temporary directory location
-        // private static final File tmpdir = new File(AccessController
-        //     .doPrivileged(new GetPropertyAction("java.io.tmpdir")));
-        // static File location() {
-        //     return tmpdir;
-        // }
+        // temporary directory location.
+        /*
+        private static final File tmpdir = new File(AccessController
+           .doPrivileged(new GetPropertyAction("java.io.tmpdir")));
+        static File location() {
+            return tmpdir;
+        }
+        */
 
         // file name generation
+        // private static final SecureRandom random = new SecureRandom();
         static File generateFile(String prefix, String suffix, File dir)
             throws IOException
         {
@@ -1885,7 +1900,7 @@
 
             // Android-changed: Reject invalid file prefixes
             // Use only the file name from the supplied prefix
-            //prefix = (new File(prefix)).getName();
+            // prefix = (new File(prefix)).getName();
 
             String name = prefix + Long.toString(n) + suffix;
             File f = new File(dir, name);
@@ -1977,7 +1992,7 @@
         if (suffix == null)
             suffix = ".tmp";
 
-
+        // Android-changed: Handle java.io.tmpdir changes.
         File tmpdir = (directory != null) ? directory
                                           : new File(System.getProperty("java.io.tmpdir", "."));
         //SecurityManager sm = System.getSecurityManager();
@@ -1985,17 +2000,19 @@
         do {
             f = TempDirectory.generateFile(prefix, suffix, tmpdir);
 
-            // Android change: sm is always null on android
-            // if (sm != null) {
-            //     try {
-            //         sm.checkWrite(f.getPath());
-            //     } catch (SecurityException se) {
-            //         // don't reveal temporary directory location
-            //         if (directory == null)
-            //             throw new SecurityException("Unable to create temporary file");
-            //         throw se;
-            //     }
-            // }
+            // Android-changed: sm is always null on Android.
+            /*
+            if (sm != null) {
+                try {
+                    sm.checkWrite(f.getPath());
+                } catch (SecurityException se) {
+                    // don't reveal temporary directory location
+                    if (directory == null)
+                        throw new SecurityException("Unable to create temporary file");
+                    throw se;
+                }
+            }
+            */
         } while ((fs.getBooleanAttributes(f) & FileSystem.BA_EXISTS) != 0);
 
         if (!fs.createFileExclusively(f.getPath()))
diff --git a/java/io/RandomAccessFile.java b/java/io/RandomAccessFile.java
index f4830c1..df607cb 100644
--- a/java/io/RandomAccessFile.java
+++ b/java/io/RandomAccessFile.java
@@ -68,7 +68,6 @@
     // BEGIN Android-added: CloseGuard and some helper fields for Android changes in this file.
     private final CloseGuard guard = CloseGuard.get();
     private final byte[] scratch = new byte[8];
-    private boolean syncMetadata = false;
     private int mode;
     // END Android-added: CloseGuard and some helper fields for Android changes in this file.
 
@@ -231,13 +230,9 @@
             rw = true;
             if (mode.length() > 2) {
                 if (mode.equals("rws")) {
-                    // Android-changed: Don't use O_SYNC for "rws". Is this correct?
-                    // imode |= O_SYNC;
-                    syncMetadata = true;
-                } else if (mode.equals("rwd")) {
-                    // Android-changed: Use O_SYNC rather than O_DSYNC for "rwd". Is this correct?
-                    // imode |= O_DSYNC;
                     imode |= O_SYNC;
+                } else if (mode.equals("rwd")) {
+                    imode |= O_DSYNC;
                 } else {
                     imode = -1;
                 }
@@ -272,13 +267,6 @@
 
         // BEGIN Android-changed: Use IoBridge.open() instead of open.
         fd = IoBridge.open(name, imode);
-        if (syncMetadata) {
-            try {
-                fd.sync();
-            } catch (IOException e) {
-                // Ignored
-            }
-        }
         guard.open("close");
         // END Android-changed: Use IoBridge.open() instead of open.
     }
@@ -515,10 +503,6 @@
         // Android-changed: Implement on top of low-level API, not directly natively.
         ioTracker.trackIo(len, IoTracker.Mode.WRITE);
         IoBridge.write(fd, b, off, len);
-        // if we are in "rws" mode, attempt to sync file+metadata
-        if (syncMetadata) {
-            fd.sync();
-        }
     }
 
     /**
@@ -643,10 +627,6 @@
         if (filePointer > newLength) {
             seek(newLength);
         }
-        // if we are in "rws" mode, attempt to sync file+metadata
-        if (syncMetadata) {
-            fd.sync();
-        }
         // END Android-changed: Implement on top of low-level API, not directly natively.
     }
 
diff --git a/java/lang/String.java b/java/lang/String.java
index eb24712..4cefed8 100644
--- a/java/lang/String.java
+++ b/java/lang/String.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 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
@@ -23,27 +23,23 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
+
 package java.lang;
 
 import dalvik.annotation.optimization.FastNative;
 import java.io.ObjectStreamField;
 import java.io.UnsupportedEncodingException;
-import java.lang.ArrayIndexOutOfBoundsException;
 import java.nio.charset.Charset;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Formatter;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.StringJoiner;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
 import libcore.util.CharsetUtils;
-import libcore.util.EmptyArray;
 
 /**
  * The {@code String} class represents character strings. All
@@ -117,11 +113,26 @@
 public final class String
     implements java.io.Serializable, Comparable<String>, CharSequence {
 
-    // The associated character storage is managed by the runtime. We only
-    // keep track of the length here.
+    // BEGIN Android-changed: The character data is managed by the runtime.
+    // We only keep track of the length here and compression here. This has several consequences
+    // throughout this class:
+    //  - References to value[i] are replaced by charAt(i).
+    //  - References to value.length are replaced by calls to length().
+    //  - Sometimes the result of length() is assigned to a local variable to avoid repeated calls.
+    //  - We skip several attempts at optimization where the values field was assigned to a local
+    //    variable to avoid the getfield opcode.
+    // These changes are not all marked individually.
     //
     // private final char value[];
+    //
+    // If STRING_COMPRESSION_ENABLED, count stores the length shifted one bit to the left with the
+    // lowest bit used to indicate whether or not the bytes are compressed (see GetFlaggedCount in
+    // the native code).
     private final int count;
+    // END Android-changed: The character data is managed by the runtime.
+
+    // Android-changed: We make use of new StringIndexOutOfBoundsException constructor signatures.
+    // These improve some error messages. These changes are not all marked individually.
 
     /** Cache the hash code for the string */
     private int hash; // Default to 0
@@ -145,6 +156,7 @@
      * unnecessary since Strings are immutable.
      */
     public String() {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -159,6 +171,7 @@
      *         A {@code String}
      */
     public String(String original) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -172,6 +185,7 @@
      *         The initial value of the string
      */
     public String(char value[]) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -197,6 +211,7 @@
      *          characters outside the bounds of the {@code value} array
      */
     public String(char value[], int offset, int count) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -229,6 +244,7 @@
      * @since  1.5
      */
     public String(int[] codePoints, int offset, int count) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -273,6 +289,7 @@
      */
     @Deprecated
     public String(byte ascii[], int hibyte, int offset, int count) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -308,6 +325,7 @@
      */
     @Deprecated
     public String(byte ascii[], int hibyte) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -346,6 +364,7 @@
      */
     public String(byte bytes[], int offset, int length, String charsetName)
             throws UnsupportedEncodingException {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -380,6 +399,7 @@
      * @since  1.6
      */
     public String(byte bytes[], int offset, int length, Charset charset) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -408,6 +428,7 @@
      */
     public String(byte bytes[], String charsetName)
             throws UnsupportedEncodingException {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -432,6 +453,7 @@
      * @since  1.6
      */
     public String(byte bytes[], Charset charset) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -462,6 +484,7 @@
      * @since  JDK1.1
      */
     public String(byte bytes[], int offset, int length) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -482,6 +505,7 @@
      * @since  JDK1.1
      */
     public String(byte bytes[]) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -495,6 +519,7 @@
      *         A {@code StringBuffer}
      */
     public String(StringBuffer buffer) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
@@ -514,10 +539,12 @@
      * @since  1.5
      */
     public String(StringBuilder builder) {
+        // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
         throw new UnsupportedOperationException("Use StringFactory instead.");
     }
 
 
+    // BEGIN Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime.
     /**
      * Package private constructor
      *
@@ -527,6 +554,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.
 
     /**
      * Returns the length of this string.
@@ -537,6 +565,8 @@
      *          object.
      */
     public int length() {
+        // BEGIN Android-changed: Get length from count field rather than value array (see above).
+        // return value.length;
         final boolean STRING_COMPRESSION_ENABLED = true;
         if (STRING_COMPRESSION_ENABLED) {
             // For the compression purposes (save the characters as 8-bit if all characters
@@ -545,6 +575,7 @@
         } else {
             return count;
         }
+        // END Android-changed: Get length from count field rather than value array (see above).
     }
 
     /**
@@ -556,7 +587,9 @@
      * @since 1.6
      */
     public boolean isEmpty() {
+        // Android-changed: Get length from count field rather than value array (see above).
         // Empty string has {@code count == 0} with or without string compression enabled.
+        // return value.length == 0;
         return count == 0;
     }
 
@@ -578,8 +611,10 @@
      *             argument is negative or not less than the length of this
      *             string.
      */
+    // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
     @FastNative
     public native char charAt(int index);
+    // END Android-changed: Replace with implementation in runtime to access chars (see above).
 
     /**
      * Returns the character (Unicode code point) at the specified
@@ -593,7 +628,7 @@
      * {@code char} value at the following index is in the
      * low-surrogate range, then the supplementary code point
      * corresponding to this surrogate pair is returned. Otherwise,
-     * the {@code char} value at the given index is returned
+     * the {@code char} value at the given index is returned.
      *
      * @param      index the index to the {@code char} values
      * @return     the code point value of the character at the
@@ -607,6 +642,7 @@
         if ((index < 0) || (index >= length())) {
             throw new StringIndexOutOfBoundsException(index);
         }
+        // Android-changed: Skip codePointAtImpl optimization that needs access to java chars.
         return Character.codePointAt(this, index);
     }
 
@@ -637,6 +673,7 @@
         if ((i < 0) || (i >= length())) {
             throw new StringIndexOutOfBoundsException(index);
         }
+        // Android-changed: Skip codePointBeforeImpl optimization that needs access to java chars.
         return Character.codePointBefore(this, index);
     }
 
@@ -665,6 +702,7 @@
         if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
             throw new IndexOutOfBoundsException();
         }
+        // Android-changed: Skip codePointCountImpl optimization that needs access to java chars.
         return Character.codePointCount(this, beginIndex, endIndex);
     }
 
@@ -692,6 +730,7 @@
         if (index < 0 || index > length()) {
             throw new IndexOutOfBoundsException();
         }
+        // Android-changed: Skip offsetByCodePointsImpl optimization that needs access to java chars
         return Character.offsetByCodePoints(this, index, codePointOffset);
     }
 
@@ -700,6 +739,7 @@
      * This method doesn't perform any range checking.
      */
     void getChars(char dst[], int dstBegin) {
+        // Android-changed: Replace arraycopy with native call since chars are managed by runtime.
         getCharsNoCheck(0, length(), dst, dstBegin);
     }
 
@@ -734,6 +774,7 @@
      *                {@code dst.length}</ul>
      */
     public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
+        // BEGIN Android-changed: Implement in terms of length() and native getCharsNoCheck method.
         if (dst == null) {
             throw new NullPointerException("dst == null");
         }
@@ -766,8 +807,10 @@
         }
 
         getCharsNoCheck(srcBegin, srcEnd, dst, dstBegin);
+        // END Android-changed: Implement in terms of length() and native getCharsNoCheck method.
     }
 
+    // BEGIN Android-added: Native method to access char storage managed by runtime.
     /**
      * getChars without bounds checks, for use by other classes
      * within the java.lang package only.  The caller is responsible for
@@ -775,7 +818,7 @@
      */
     @FastNative
     native void getCharsNoCheck(int start, int end, char[] buffer, int index);
-
+    // END Android-added: Native method to access char storage managed by runtime.
 
     /**
      * Copies characters from this string into the destination byte array. Each
@@ -829,7 +872,7 @@
             throw new StringIndexOutOfBoundsException(this, srcEnd);
         }
         if (srcBegin > srcEnd) {
-            throw new StringIndexOutOfBoundsException(this, (srcEnd - srcBegin));
+            throw new StringIndexOutOfBoundsException(this, srcEnd - srcBegin);
         }
 
         int j = dstBegin;
@@ -864,6 +907,8 @@
     public byte[] getBytes(String charsetName)
             throws UnsupportedEncodingException {
         if (charsetName == null) throw new NullPointerException();
+        // Android-changed: Skip StringCoding optimization that needs access to java chars.
+        // return StringCoding.encode(charsetName, value, 0, value.length);
         return getBytes(Charset.forNameUEE(charsetName));
     }
 
@@ -886,6 +931,9 @@
      * @since  1.6
      */
     public byte[] getBytes(Charset charset) {
+        // BEGIN Android-changed: Skip StringCoding optimization that needs access to java chars.
+        // if (charset == null) throw new NullPointerException();
+        // return StringCoding.encode(charset, value, 0, value.length);
         if (charset == null) {
             throw new NullPointerException("charset == null");
         }
@@ -906,6 +954,7 @@
         byte[] bytes = new byte[buffer.limit()];
         buffer.get(bytes);
         return bytes;
+        // END Android-changed: Skip StringCoding optimization that needs access to java chars.
     }
 
     /**
@@ -922,6 +971,8 @@
      * @since      JDK1.1
      */
     public byte[] getBytes() {
+        // Android-changed: Skip StringCoding optimization that needs access to java chars.
+        // return StringCoding.encode(value, 0, value.length);
         return getBytes(Charset.defaultCharset());
     }
 
@@ -945,7 +996,7 @@
             return true;
         }
         if (anObject instanceof String) {
-            String anotherString = (String) anObject;
+            String anotherString = (String)anObject;
             int n = length();
             if (n == anotherString.length()) {
                 int i = 0;
@@ -1114,8 +1165,10 @@
      *          value greater than {@code 0} if this string is
      *          lexicographically greater than the string argument.
      */
+    // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
     @FastNative
     public native int compareTo(String anotherString);
+    // END Android-changed: Replace with implementation in runtime to access chars (see above).
 
     /**
      * A Comparator that orders {@code String} objects as by
@@ -1506,8 +1559,11 @@
         }
     }
 
+    // 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.
@@ -1655,6 +1711,7 @@
      *          or {@code -1} if there is no such occurrence.
      */
     public int indexOf(String str, int fromIndex) {
+        // Android-changed: Change parameters to static indexOf to match new signature below.
         return indexOf(this, str, fromIndex);
     }
 
@@ -1667,6 +1724,8 @@
      * @param   target       the characters being searched for.
      * @param   fromIndex    the index to begin searching from.
      */
+    // BEGIN Android-changed: Change signature to take String object rather than char arrays.
+    // The implementation using a java char array is replaced with one using length() & charAt().
     static int indexOf(String source,
                        String target,
                        int fromIndex) {
@@ -1706,6 +1765,7 @@
         }
         return -1;
     }
+    // END Android-changed: Change signature to take String object rather than char arrays.
 
     /**
      * Code shared by String and StringBuffer to do searches. The
@@ -1794,6 +1854,7 @@
      *          or {@code -1} if there is no such occurrence.
      */
     public int lastIndexOf(String str, int fromIndex) {
+        // Android-changed: Change parameters to static lastIndexOf to match new signature below.
         return lastIndexOf(this, str, fromIndex);
     }
 
@@ -1806,6 +1867,8 @@
      * @param   target       the characters being searched for.
      * @param   fromIndex    the index to begin searching from.
      */
+    // BEGIN Android-changed: Change signature to take String object rather than char arrays.
+    // The implementation using a java char array is replaced with one using length() & charAt().
     static int lastIndexOf(String source,
                            String target,
                            int fromIndex) {
@@ -1853,6 +1916,7 @@
             return start + 1;
         }
     }
+    // END Android-changed: Change signature to take String object rather than char arrays.
 
     /**
      * Code shared by String and StringBuffer to do searches. The
@@ -1938,6 +2002,7 @@
         if (subLen < 0) {
             throw new StringIndexOutOfBoundsException(this, beginIndex);
         }
+        // Android-changed: Use native fastSubstring instead of String constructor.
         return (beginIndex == 0) ? this : fastSubstring(beginIndex, subLen);
     }
 
@@ -1975,12 +2040,15 @@
             throw new StringIndexOutOfBoundsException(subLen);
         }
 
+        // Android-changed: Use native fastSubstring instead of String constructor.
         return ((beginIndex == 0) && (endIndex == length())) ? this
                 : fastSubstring(beginIndex, subLen);
     }
 
+    // BEGIN Android-added: Native method to access char storage managed by runtime.
     @FastNative
     private native String fastSubstring(int start, int length);
+    // END Android-added: Native method to access char storage managed by runtime.
 
     /**
      * Returns a character sequence that is a subsequence of this sequence.
@@ -2035,8 +2103,10 @@
      * @return  a string that represents the concatenation of this object's
      *          characters followed by the string argument's characters.
      */
+    // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
     @FastNative
     public native String concat(String str);
+    // END Android-changed: Replace with implementation in runtime to access chars (see above).
 
     /**
      * Returns a string resulting from replacing all occurrences of
@@ -2068,6 +2138,7 @@
      *          occurrence of {@code oldChar} with {@code newChar}.
      */
     public String replace(char oldChar, char newChar) {
+        // BEGIN Android-changed: Replace with implementation using native doReplace method.
         if (oldChar != newChar) {
             final int len = length();
             for (int i = 0; i < len; ++i) {
@@ -2076,12 +2147,15 @@
                 }
             }
         }
+        // END Android-changed: Replace with implementation using native doReplace method.
         return this;
     }
 
+    // BEGIN Android-added: Native method to access char storage managed by runtime.
     // Implementation of replace(char oldChar, char newChar) called when we found a match.
     @FastNative
     private native String doReplace(char oldChar, char newChar);
+    // END Android-added: Native method to access char storage managed by runtime.
 
     /**
      * Tells whether or not this string matches the given <a
@@ -2226,11 +2300,10 @@
      * @param  target The sequence of char values to be replaced
      * @param  replacement The replacement sequence of char values
      * @return  The resulting string
-     * @throws NullPointerException if <code>target</code> or
-     *         <code>replacement</code> is <code>null</code>.
      * @since 1.5
      */
     public String replace(CharSequence target, CharSequence replacement) {
+        // BEGIN Android-changed: Replace regex-based implementation with a bespoke one.
         if (target == null) {
             throw new NullPointerException("target == null");
         }
@@ -2286,6 +2359,7 @@
         } else {
             return this;
         }
+        // END Android-changed: Replace regex-based implementation with a bespoke one.
     }
 
     /**
@@ -2375,12 +2449,13 @@
      * @spec JSR-51
      */
     public String[] split(String regex, int limit) {
+        // BEGIN Android-changed: Replace custom fast-path with use of new Pattern.fastSplit method.
         // Try fast splitting without allocating Pattern object
         String[] fast = Pattern.fastSplit(regex, this, limit);
         if (fast != null) {
             return fast;
         }
-
+        // END Android-changed: Replace custom fast-path with use of new Pattern.fastSplit method.
         return Pattern.compile(regex).split(this, limit);
     }
 
@@ -2563,6 +2638,7 @@
      * @since   1.1
      */
     public String toLowerCase(Locale locale) {
+        // Android-changed: Replace custom code with call to new CaseMapper class.
         return CaseMapper.toLowerCase(locale, this);
     }
 
@@ -2638,6 +2714,7 @@
      * @since   1.1
      */
     public String toUpperCase(Locale locale) {
+        // Android-changed: Replace custom code with call to new CaseMapper class.
         return CaseMapper.toUpperCase(locale, this, length());
     }
 
@@ -2724,8 +2801,10 @@
      *          of this string and whose contents are initialized to contain
      *          the character sequence represented by this string.
      */
+    // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
     @FastNative
     public native char[] toCharArray();
+    // END Android-changed: Replace with implementation in runtime to access chars (see above).
 
 
     /**
@@ -2758,9 +2837,6 @@
      *          href="../util/Formatter.html#detail">Details</a> section of the
      *          formatter class specification.
      *
-     * @throws  NullPointerException
-     *          If the <tt>format</tt> is <tt>null</tt>
-     *
      * @return  A formatted string
      *
      * @see  java.util.Formatter
@@ -2802,9 +2878,6 @@
      *          href="../util/Formatter.html#detail">Details</a> section of the
      *          formatter class specification
      *
-     * @throws  NullPointerException
-     *          If the <tt>format</tt> is <tt>null</tt>
-     *
      * @return  A formatted string
      *
      * @see  java.util.Formatter
@@ -2838,6 +2911,8 @@
      *          character array.
      */
     public static String valueOf(char data[]) {
+        // Android-changed: Replace constructor call with call to new StringFactory class.
+        // return new String(data);
         return StringFactory.newStringFromChars(data);
     }
 
@@ -2862,6 +2937,8 @@
      *          {@code data.length}.
      */
     public static String valueOf(char data[], int offset, int count) {
+        // Android-changed: Replace constructor call with call to new StringFactory class.
+        // return new String(data, offset, count);
         return StringFactory.newStringFromChars(data, offset, count);
     }
 
@@ -2879,7 +2956,9 @@
      *          {@code data.length}.
      */
     public static String copyValueOf(char data[], int offset, int count) {
+        // Android-changed: Replace constructor call with call to new StringFactory class.
         // All public String constructors now copy the data.
+        // return new String(data, offset, count);
         return StringFactory.newStringFromChars(data, offset, count);
     }
 
@@ -2891,6 +2970,8 @@
      *          character array.
      */
     public static String copyValueOf(char data[]) {
+        // Android-changed: Replace constructor call with call to new StringFactory class.
+        // return new String(data);
         return StringFactory.newStringFromChars(data);
     }
 
@@ -2915,6 +2996,9 @@
      *          as its single character the argument {@code c}.
      */
     public static String valueOf(char c) {
+        // Android-changed: Replace constructor call with call to new StringFactory class.
+        // char data[] = {c};
+        // return new String(data, true);
         return StringFactory.newStringFromChars(0, 1, new char[] { c });
     }
 
@@ -2997,6 +3081,8 @@
      * @return  a string that has the same contents as this string, but is
      *          guaranteed to be from a pool of unique strings.
      */
+    // BEGIN Android-changed: Annotate native method as @FastNative.
     @FastNative
+    // END Android-changed: Annotate native method as @FastNative.
     public native String intern();
 }
diff --git a/java/lang/StringCoding.java b/java/lang/StringCoding.java
deleted file mode 100644
index 54abaed..0000000
--- a/java/lang/StringCoding.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2000, 2012, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.lang;
-
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.SoftReference;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.CoderResult;
-import java.nio.charset.CodingErrorAction;
-import java.nio.charset.IllegalCharsetNameException;
-import java.nio.charset.UnsupportedCharsetException;
-import java.util.Arrays;
-import sun.misc.MessageUtils;
-import sun.nio.cs.HistoricallyNamedCharset;
-import sun.nio.cs.ArrayDecoder;
-import sun.nio.cs.ArrayEncoder;
-
-/**
- * Utility class for string encoding and decoding.
- */
-
-class StringCoding {
-
-    private StringCoding() { }
-
-    /** The cached coders for each thread */
-    private final static ThreadLocal<SoftReference<StringDecoder>> decoder =
-        new ThreadLocal<>();
-    private final static ThreadLocal<SoftReference<StringEncoder>> encoder =
-        new ThreadLocal<>();
-
-    private static boolean warnUnsupportedCharset = true;
-
-    private static <T> T deref(ThreadLocal<SoftReference<T>> tl) {
-        SoftReference<T> sr = tl.get();
-        if (sr == null)
-            return null;
-        return sr.get();
-    }
-
-    private static <T> void set(ThreadLocal<SoftReference<T>> tl, T ob) {
-        tl.set(new SoftReference<T>(ob));
-    }
-
-    // Trim the given byte array to the given length
-    //
-    private static byte[] safeTrim(byte[] ba, int len, Charset cs, boolean isTrusted) {
-
-        // Android-changed: System.getSecurityManager() == null is always true on Android.
-        // Libcore tests expect a defensive copy in pretty much all cases.
-        // if (len == ba.length && (isTrusted || System.getSecurityManager() == null))
-        if (len == ba.length && (isTrusted))
-            return ba;
-        else
-            return Arrays.copyOf(ba, len);
-    }
-
-    // Trim the given char array to the given length
-    //
-    private static char[] safeTrim(char[] ca, int len,
-                                   Charset cs, boolean isTrusted) {
-        // Android-changed: System.getSecurityManager() == null is always true on Android.
-        // Libcore tests expect a defensive copy in pretty much all cases.
-        // if (len == ca.length && (isTrusted || System.getSecurityManager() == null))
-        if (len == ca.length && (isTrusted))
-            return ca;
-        else
-            return Arrays.copyOf(ca, len);
-    }
-
-    private static int scale(int len, float expansionFactor) {
-        // We need to perform double, not float, arithmetic; otherwise
-        // we lose low order bits when len is larger than 2**24.
-        return (int)(len * (double)expansionFactor);
-    }
-
-    private static Charset lookupCharset(String csn) {
-        if (Charset.isSupported(csn)) {
-            try {
-                return Charset.forName(csn);
-            } catch (UnsupportedCharsetException x) {
-                throw new Error(x);
-            }
-        }
-        return null;
-    }
-
-    private static void warnUnsupportedCharset(String csn) {
-        if (warnUnsupportedCharset) {
-            // Use sun.misc.MessageUtils rather than the Logging API or
-            // System.err since this method may be called during VM
-            // initialization before either is available.
-            MessageUtils.err("WARNING: Default charset " + csn +
-                             " not supported, using ISO-8859-1 instead");
-            warnUnsupportedCharset = false;
-        }
-    }
-
-
-    // -- Decoding --
-    private static class StringDecoder {
-        private final String requestedCharsetName;
-        private final Charset cs;
-        private final CharsetDecoder cd;
-        private final boolean isTrusted;
-
-        private StringDecoder(Charset cs, String rcn) {
-            this.requestedCharsetName = rcn;
-            this.cs = cs;
-            this.cd = cs.newDecoder()
-                .onMalformedInput(CodingErrorAction.REPLACE)
-                .onUnmappableCharacter(CodingErrorAction.REPLACE);
-            this.isTrusted = (cs.getClass().getClassLoader() == null);
-        }
-
-        String charsetName() {
-            if (cs instanceof HistoricallyNamedCharset)
-                return ((HistoricallyNamedCharset)cs).historicalName();
-            return cs.name();
-        }
-
-        final String requestedCharsetName() {
-            return requestedCharsetName;
-        }
-
-        char[] decode(byte[] ba, int off, int len) {
-            int en = scale(len, cd.maxCharsPerByte());
-            char[] ca = new char[en];
-            if (len == 0)
-                return ca;
-            if (cd instanceof ArrayDecoder) {
-                int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca);
-                return safeTrim(ca, clen, cs, isTrusted);
-            } else {
-                cd.reset();
-                ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
-                CharBuffer cb = CharBuffer.wrap(ca);
-                try {
-                    CoderResult cr = cd.decode(bb, cb, true);
-                    if (!cr.isUnderflow())
-                        cr.throwException();
-                    cr = cd.flush(cb);
-                    if (!cr.isUnderflow())
-                        cr.throwException();
-                } catch (CharacterCodingException x) {
-                    // Substitution is always enabled,
-                    // so this shouldn't happen
-                    throw new Error(x);
-                }
-                return safeTrim(ca, cb.position(), cs, isTrusted);
-            }
-        }
-    }
-
-    static char[] decode(String charsetName, byte[] ba, int off, int len)
-        throws UnsupportedEncodingException
-    {
-        StringDecoder sd = deref(decoder);
-        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
-        if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
-                              || csn.equals(sd.charsetName()))) {
-            sd = null;
-            try {
-                Charset cs = lookupCharset(csn);
-                if (cs != null)
-                    sd = new StringDecoder(cs, csn);
-            } catch (IllegalCharsetNameException x) {}
-            if (sd == null)
-                throw new UnsupportedEncodingException(csn);
-            set(decoder, sd);
-        }
-        return sd.decode(ba, off, len);
-    }
-
-    static char[] decode(Charset cs, byte[] ba, int off, int len) {
-        // (1)We never cache the "external" cs, the only benefit of creating
-        // an additional StringDe/Encoder object to wrap it is to share the
-        // de/encode() method. These SD/E objects are short-lifed, the young-gen
-        // gc should be able to take care of them well. But the best approash
-        // is still not to generate them if not really necessary.
-        // (2)The defensive copy of the input byte/char[] has a big performance
-        // impact, as well as the outgoing result byte/char[]. Need to do the
-        // optimization check of (sm==null && classLoader0==null) for both.
-        // (3)getClass().getClassLoader0() is expensive
-        // (4)There might be a timing gap in isTrusted setting. getClassLoader0()
-        // is only chcked (and then isTrusted gets set) when (SM==null). It is
-        // possible that the SM==null for now but then SM is NOT null later
-        // when safeTrim() is invoked...the "safe" way to do is to redundant
-        // check (... && (isTrusted || SM == null || getClassLoader0())) in trim
-        // but it then can be argued that the SM is null when the opertaion
-        // is started...
-        CharsetDecoder cd = cs.newDecoder();
-        int en = scale(len, cd.maxCharsPerByte());
-        char[] ca = new char[en];
-        if (len == 0)
-            return ca;
-        boolean isTrusted = false;
-        if (System.getSecurityManager() != null) {
-            if (!(isTrusted = (cs.getClass().getClassLoader() == null))) {
-                ba =  Arrays.copyOfRange(ba, off, off + len);
-                off = 0;
-            }
-        }
-        cd.onMalformedInput(CodingErrorAction.REPLACE)
-          .onUnmappableCharacter(CodingErrorAction.REPLACE)
-          .reset();
-        if (cd instanceof ArrayDecoder) {
-            int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca);
-            return safeTrim(ca, clen, cs, isTrusted);
-        } else {
-            ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
-            CharBuffer cb = CharBuffer.wrap(ca);
-            try {
-                CoderResult cr = cd.decode(bb, cb, true);
-                if (!cr.isUnderflow())
-                    cr.throwException();
-                cr = cd.flush(cb);
-                if (!cr.isUnderflow())
-                    cr.throwException();
-            } catch (CharacterCodingException x) {
-                // Substitution is always enabled,
-                // so this shouldn't happen
-                throw new Error(x);
-            }
-            return safeTrim(ca, cb.position(), cs, isTrusted);
-        }
-    }
-
-    static char[] decode(byte[] ba, int off, int len) {
-        String csn = Charset.defaultCharset().name();
-        try {
-            // use charset name decode() variant which provides caching.
-            return decode(csn, ba, off, len);
-        } catch (UnsupportedEncodingException x) {
-            warnUnsupportedCharset(csn);
-        }
-        try {
-            return decode("ISO-8859-1", ba, off, len);
-        } catch (UnsupportedEncodingException x) {
-            // If this code is hit during VM initialization, MessageUtils is
-            // the only way we will be able to get any kind of error message.
-            MessageUtils.err("ISO-8859-1 charset not available: "
-                             + x.toString());
-            // If we can not find ISO-8859-1 (a required encoding) then things
-            // are seriously wrong with the installation.
-            System.exit(1);
-            return null;
-        }
-    }
-
-    // -- Encoding --
-    private static class StringEncoder {
-        private Charset cs;
-        private CharsetEncoder ce;
-        private final String requestedCharsetName;
-        private final boolean isTrusted;
-
-        private StringEncoder(Charset cs, String rcn) {
-            this.requestedCharsetName = rcn;
-            this.cs = cs;
-            this.ce = cs.newEncoder()
-                .onMalformedInput(CodingErrorAction.REPLACE)
-                .onUnmappableCharacter(CodingErrorAction.REPLACE);
-            this.isTrusted = (cs.getClass().getClassLoader() == null);
-        }
-
-        String charsetName() {
-            if (cs instanceof HistoricallyNamedCharset)
-                return ((HistoricallyNamedCharset)cs).historicalName();
-            return cs.name();
-        }
-
-        final String requestedCharsetName() {
-            return requestedCharsetName;
-        }
-
-        byte[] encode(char[] ca, int off, int len) {
-            int en = scale(len, ce.maxBytesPerChar());
-            byte[] ba = new byte[en];
-            if (len == 0)
-                return ba;
-            if (ce instanceof ArrayEncoder) {
-                int blen = ((ArrayEncoder)ce).encode(ca, off, len, ba);
-                return safeTrim(ba, blen, cs, isTrusted);
-            } else {
-                ce.reset();
-                ByteBuffer bb = ByteBuffer.wrap(ba);
-                CharBuffer cb = CharBuffer.wrap(ca, off, len);
-                try {
-                    // Android-changed:  Pass read-only buffer, so the encoder can't alter it
-                    CoderResult cr = ce.encode(cb.asReadOnlyBuffer(), bb, true);
-                    if (!cr.isUnderflow())
-                        cr.throwException();
-                    cr = ce.flush(bb);
-                    if (!cr.isUnderflow())
-                        cr.throwException();
-                } catch (CharacterCodingException x) {
-                    // Substitution is always enabled,
-                    // so this shouldn't happen
-                    throw new Error(x);
-                }
-                return safeTrim(ba, bb.position(), cs, isTrusted);
-            }
-        }
-    }
-
-    static byte[] encode(String charsetName, char[] ca, int off, int len)
-        throws UnsupportedEncodingException
-    {
-        StringEncoder se = deref(encoder);
-        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
-        if ((se == null) || !(csn.equals(se.requestedCharsetName())
-                              || csn.equals(se.charsetName()))) {
-            se = null;
-            try {
-                Charset cs = lookupCharset(csn);
-                if (cs != null)
-                    se = new StringEncoder(cs, csn);
-            } catch (IllegalCharsetNameException x) {}
-            if (se == null)
-                throw new UnsupportedEncodingException (csn);
-            set(encoder, se);
-        }
-        return se.encode(ca, off, len);
-    }
-
-    static byte[] encode(Charset cs, char[] ca, int off, int len) {
-        CharsetEncoder ce = cs.newEncoder();
-        int en = scale(len, ce.maxBytesPerChar());
-        byte[] ba = new byte[en];
-        if (len == 0)
-            return ba;
-        boolean isTrusted = false;
-        if (System.getSecurityManager() != null) {
-            if (!(isTrusted = (cs.getClass().getClassLoader() == null))) {
-                ca =  Arrays.copyOfRange(ca, off, off + len);
-                off = 0;
-            }
-        }
-        ce.onMalformedInput(CodingErrorAction.REPLACE)
-          .onUnmappableCharacter(CodingErrorAction.REPLACE)
-          .reset();
-        if (ce instanceof ArrayEncoder) {
-            int blen = ((ArrayEncoder)ce).encode(ca, off, len, ba);
-            return safeTrim(ba, blen, cs, isTrusted);
-        } else {
-            ByteBuffer bb = ByteBuffer.wrap(ba);
-            CharBuffer cb = CharBuffer.wrap(ca, off, len);
-            try {
-                // Android-changed:  Pass read-only buffer, so the encoder can't alter it
-                CoderResult cr = ce.encode(cb.asReadOnlyBuffer(), bb, true);
-                if (!cr.isUnderflow())
-                    cr.throwException();
-                cr = ce.flush(bb);
-                if (!cr.isUnderflow())
-                    cr.throwException();
-            } catch (CharacterCodingException x) {
-                throw new Error(x);
-            }
-            return safeTrim(ba, bb.position(), cs, isTrusted);
-        }
-    }
-
-    static byte[] encode(char[] ca, int off, int len) {
-        String csn = Charset.defaultCharset().name();
-        try {
-            // use charset name encode() variant which provides caching.
-            return encode(csn, ca, off, len);
-        } catch (UnsupportedEncodingException x) {
-            warnUnsupportedCharset(csn);
-        }
-        try {
-            return encode("ISO-8859-1", ca, off, len);
-        } catch (UnsupportedEncodingException x) {
-            // If this code is hit during VM initialization, MessageUtils is
-            // the only way we will be able to get any kind of error message.
-            MessageUtils.err("ISO-8859-1 charset not available: "
-                             + x.toString());
-            // If we can not find ISO-8859-1 (a required encoding) then things
-            // are seriously wrong with the installation.
-            System.exit(1);
-            return null;
-        }
-    }
-}
diff --git a/java/lang/StringIndexOutOfBoundsException.java b/java/lang/StringIndexOutOfBoundsException.java
index 4fdd061..a40bd29 100644
--- a/java/lang/StringIndexOutOfBoundsException.java
+++ b/java/lang/StringIndexOutOfBoundsException.java
@@ -70,11 +70,12 @@
         super("String index out of range: " + index);
     }
 
+    // BEGIN Android-added: Additional constructors for internal use.
     /**
      * Used internally for consistent high-quality error reporting.
      * @hide
      */
-    public StringIndexOutOfBoundsException(String s, int index) {
+    StringIndexOutOfBoundsException(String s, int index) {
         this(s.length(), index);
     }
 
@@ -82,7 +83,7 @@
      * Used internally for consistent high-quality error reporting.
      * @hide
      */
-    public StringIndexOutOfBoundsException(int sourceLength, int index) {
+    StringIndexOutOfBoundsException(int sourceLength, int index) {
         super("length=" + sourceLength + "; index=" + index);
     }
 
@@ -90,7 +91,7 @@
      * Used internally for consistent high-quality error reporting.
      * @hide
      */
-    public StringIndexOutOfBoundsException(String s, int offset, int count) {
+    StringIndexOutOfBoundsException(String s, int offset, int count) {
         this(s.length(), offset, count);
     }
 
@@ -98,9 +99,10 @@
      * Used internally for consistent high-quality error reporting.
      * @hide
      */
-    public StringIndexOutOfBoundsException(int sourceLength, int offset,
+    StringIndexOutOfBoundsException(int sourceLength, int offset,
             int count) {
         super("length=" + sourceLength + "; regionStart=" + offset
                 + "; regionLength=" + count);
     }
+    // END Android-added: Additional constructors for internal use.
 }
diff --git a/java/lang/invoke/CallSite.java b/java/lang/invoke/CallSite.java
index 1ff1eb8..85b4bb9 100644
--- a/java/lang/invoke/CallSite.java
+++ b/java/lang/invoke/CallSite.java
@@ -25,15 +25,363 @@
 
 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(); }
 
-    public MethodType type() { return null; }
+    // The actual payload of this call site:
+    /*package-private*/
+    MethodHandle target;    // Note: This field is known to the JVM.  Do not change.
 
+    /**
+     * 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/MethodHandle.java b/java/lang/invoke/MethodHandle.java
index 159f9dd..af3db10 100644
--- a/java/lang/invoke/MethodHandle.java
+++ b/java/lang/invoke/MethodHandle.java
@@ -25,28 +25,1353 @@
 
 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; }
 
-    public final Object invokeExact(Object... args) throws Throwable { 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 invoke(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 Object invokeWithArguments(Object... arguments) 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(java.util.List<?> 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 MethodHandle asType(MethodType newType) { 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 asCollector(Class<?> arrayType, int arrayLength) { 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 asVarargsCollector(Class<?> arrayType) { 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 boolean isVarargsCollector() { return false; }
+    // The ArtMethod* or ArtField* associated with this method handle (used by the runtime).
+    /** @hide */ protected final long artFieldOrMethod;
 
-    public MethodHandle asFixedArity() { return null; }
+    /** @hide */
+    protected MethodHandle(long artFieldOrMethod, int handleKind, MethodType type) {
+        this.artFieldOrMethod = artFieldOrMethod;
+        this.handleKind = handleKind;
+        this.type = type;
+    }
 
-    public MethodHandle bindTo(Object x) { 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;
+        }
 
+        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 f27ad98..88ce6e0 100644
--- a/java/lang/invoke/MethodHandles.java
+++ b/java/lang/invoke/MethodHandles.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2017, 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,129 +25,3410 @@
 
 package java.lang.invoke;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
+import java.lang.reflect.*;
+import java.nio.ByteOrder;
 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 {
 
-    public static Lookup lookup() { return null; }
+    private MethodHandles() { }  // do not instantiate
 
-    public static Lookup publicLookup() { 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 <T extends Member> T
-    reflectAs(Class<T> expected, MethodHandle target) { 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.
 
-    public static final
-    class Lookup {
-        public static final int PUBLIC = 0;
+    //// Method handle creation from ordinary methods.
 
-        public static final int PRIVATE = 0;
-
-        public static final int PROTECTED = 0;
-
-        public static final int PACKAGE =  0;
-
-        public Class<?> lookupClass() { return null; }
-
-        public int lookupModes() { return 0; }
-
-        public Lookup in(Class<?> requestedLookupClass) { return null; }
-
-        public
-        MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findSpecial(Class<?> refc, String name, MethodType type,
-                                        Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle unreflect(Method m) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { return null; }
-
-        public MethodHandleInfo revealDirect(MethodHandle target) { return null; }
-
+    /**
+     * 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());
     }
 
-    public static
-    MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+    /**
+     * 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;
+    }
 
-    public static
-    MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+    /**
+     * 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());
+    }
 
+    /**
+     * 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;
+
+        /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */
+        private final int allowedModes;
+
+        /** 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;
+
+        /** 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;
+
+        /** 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;
+
+        /** 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;
+
+        private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE);
+
+        // 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);
+        }
+
+        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;
+        }
+
+        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;
+            }
+        }
+
+        /**
+         * 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");
+            }
+
+            if (type == null) {
+                throw new NullPointerException("type == null");
+            }
+
+            if (name == null) {
+                throw new NullPointerException("name == null");
+            }
+
+            if (refc == null) {
+                throw new NullPointerException("ref == null");
+            }
+
+            // Make sure that the special caller is identical to the lookup class or that we have
+            // private access.
+            checkSpecialCaller(specialCaller);
+
+            // 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.");
+            }
+
+            Method method = refc.getDeclaredMethod(name, type.ptypes());
+            checkReturnType(method, type);
+            return findSpecial(method, type, refc, specialCaller);
+        }
+
+        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);
+            }
+
+            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);
+                }
+
+                // 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);
+            }
+
+            // 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);
+            }
+
+            // Note that we restrict the receiver to "specialCaller" instances.
+            MethodType handleType = type.insertParameterTypes(0, specialCaller);
+            return createMethodHandle(method, MethodHandle.INVOKE_SUPER, handleType);
+        }
+
+        /**
+         * 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);
+        }
+
+        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);
+        }
+
+        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) { return null; }
+    MethodHandle spreadInvoker(MethodType type, int leadingArgCount) {
+        if (leadingArgCount < 0 || leadingArgCount > type.parameterCount())
+            throw newIllegalArgumentException("bad argument count", leadingArgCount);
 
+        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 null; }
+    MethodHandle exactInvoker(MethodType type) {
+        return new Transformers.Invoker(type, true /* isExactInvoker */);
+    }
 
+    /**
+     * 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 null; }
+    MethodHandle invoker(MethodType type) {
+        return new Transformers.Invoker(type, false /* isExactInvoker */);
+    }
 
+    /**
+     * 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) { return null; }
+    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);
+        }
 
+        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) { return null; }
+    MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) {
+        reorder = reorder.clone();  // get a private copy
+        MethodType oldType = target.type();
+        permuteArgumentChecks(reorder, newType, oldType);
 
+        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) { return null; }
+    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);
+        }
 
+        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) { return null; }
+    MethodHandle identity(Class<?> type) {
+        if (type == null) {
+            throw new NullPointerException("type == 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) { return null; }
+    MethodHandle insertArguments(MethodHandle target, int pos, Object... values) {
+        int insCount = values.length;
+        Class<?>[] ptypes = insertArgumentsChecks(target, insCount, pos);
+        if (insCount == 0)  {
+            return target;
+        }
 
+        // 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) { return null; }
+    MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
+        valueTypes = copyTypes(valueTypes);
+        MethodType oldType = target.type();  // get NPE
+        int dropped = dropArgumentChecks(oldType, pos, valueTypes);
 
+        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 null; }
+    MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
+        return dropArguments(target, pos, Arrays.asList(valueTypes));
+    }
 
+    /**
+     * 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) { return null; }
+    MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) {
+        filterArgumentsCheckArity(target, pos, filters);
 
+        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) { return null; }
+    MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) {
+        MethodType newType = collectArgumentsChecks(target, pos, filter);
+        return new Transformers.CollectArguments(target, filter, pos, newType);
+    }
 
+    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) { return null; }
+    MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) {
+        MethodType targetType = target.type();
+        MethodType filterType = filter.type();
+        filterReturnValueChecks(targetType, filterType);
 
+        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) { return null; }
+    MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) {
+        int foldPos = 0;
+        MethodType targetType = target.type();
+        MethodType combinerType = combiner.type();
+        Class<?> rtype = foldArgumentChecks(foldPos, targetType, combinerType);
 
+        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) { return null; }
+                               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();
+        }
 
+        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) { return null; }
+                                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);
+        }
 
+        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) { return null; }
+    MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) {
+        if (!Throwable.class.isAssignableFrom(exType))
+            throw new ClassCastException(exType.getName());
+
+        return new Transformers.AlwaysThrow(returnType, exType);
+    }
 }
diff --git a/java/lang/invoke/MethodType.java b/java/lang/invoke/MethodType.java
index 4cb5c22..bfa7ccd 100644
--- a/java/lang/invoke/MethodType.java
+++ b/java/lang/invoke/MethodType.java
@@ -25,78 +25,1227 @@
 
 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 null;
+        return makeImpl(rtype, ptypes, false);
     }
 
+    /**
+     * 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) {
-        return null;
+        boolean notrust = false;  // random List impl. could return evil ptypes array
+        return makeImpl(rtype, listToArray(ptypes), notrust);
     }
 
+    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) { return null; }
+    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);
+    }
 
+    /**
+     * 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 null; }
+    MethodType methodType(Class<?> rtype) {
+        return makeImpl(rtype, NO_PTYPES, true);
+    }
 
+    /**
+     * 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 null; }
+    MethodType methodType(Class<?> rtype, Class<?> ptype0) {
+        return makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
+    }
 
+    /**
+     * 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 null; }
+    MethodType methodType(Class<?> rtype, MethodType ptypes) {
+        return makeImpl(rtype, ptypes.ptypes, true);
+    }
 
+    /**
+     * 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) { return null; }
+    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;
+    }
 
+    /**
+     * 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 null; }
+    MethodType genericMethodType(int objectArgCount) {
+        return genericMethodType(objectArgCount, false);
+    }
 
-    public MethodType changeParameterType(int num, Class<?> nptype) { 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 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 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 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 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 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 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 appendParameterTypes(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 dropParameterTypes(int start, int end) { 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 changeReturnType(Class<?> nrtype) { 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 boolean hasPrimitives() { return false; }
+    /** 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 hasWrappers() { 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 MethodType erase() { return null; }
+    /**
+     * 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 generic() { 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 wrap() { 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 unwrap() { 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 Class<?> parameterType(int num) { 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 int parameterCount() { return 0; }
+    /**
+     * 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 Class<?> returnType() { return null; }
+    /**
+     * @return a version of the original type with MethodHandle prepended as the first argument
+     */
+    /*non-public*/ MethodType invokerType() {
+        return insertParameterTypes(0, MethodHandle.class);
+    }
 
-    public List<Class<?>> parameterList() { 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 Class<?>[] parameterArray() { return null; }
+    /*non-public*/ boolean isGeneric() {
+        return this == erase() && !hasPrimitives();
+    }
 
+    /**
+     * 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 { return null; }
+        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);
+    }
 
-    public String toMethodDescriptorString() { 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;
+            }
+
+        }
+    }
 
 }
diff --git a/java/lang/invoke/VarHandle.java b/java/lang/invoke/VarHandle.java
new file mode 100644
index 0000000..bb93fcf
--- /dev/null
+++ b/java/lang/invoke/VarHandle.java
@@ -0,0 +1,2161 @@
+/*
+ * Copyright (c) 2014, 2017, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.invoke;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A VarHandle is a dynamically strongly typed reference to a variable, or to a
+ * parametrically-defined family of variables, including static fields,
+ * non-static fields, array elements, or components of an off-heap data
+ * structure.  Access to such variables is supported under various
+ * <em>access modes</em>, including plain read/write access, volatile
+ * read/write access, and compare-and-swap.
+ *
+ * <p>VarHandles are immutable and have no visible state.  VarHandles cannot be
+ * subclassed by the user.
+ *
+ * <p>A VarHandle has:
+ * <ul>
+ * <li>a {@link #varType variable type} T, the type of every variable referenced
+ * by this VarHandle; and
+ * <li>a list of {@link #coordinateTypes coordinate types}
+ * {@code CT1, CT2, ..., CTn}, the types of <em>coordinate expressions</em> that
+ * jointly locate a variable referenced by this VarHandle.
+ * </ul>
+ * Variable and coordinate types may be primitive or reference, and are
+ * represented by {@code Class} objects.  The list of coordinate types may be
+ * empty.
+ *
+ * <p>Factory methods that produce or {@link java.lang.invoke.MethodHandles.Lookup
+ * lookup} VarHandle instances document the supported variable type and the list
+ * of coordinate types.
+ *
+ * <p>Each access mode is associated with one <em>access mode method</em>, a
+ * <a href="MethodHandle.html#sigpoly">signature polymorphic</a> method named
+ * for the access mode.  When an access mode method is invoked on a VarHandle
+ * instance, the initial arguments to the invocation are coordinate expressions
+ * that indicate in precisely which object the variable is to be accessed.
+ * Trailing arguments to the invocation represent values of importance to the
+ * access mode.  For example, the various compare-and-set or compare-and-exchange
+ * access modes require two trailing arguments for the variable's expected value
+ * and new value.
+ *
+ * <p>The arity and types of arguments to the invocation of an access mode
+ * method are not checked statically.  Instead, each access mode method
+ * specifies an {@link #accessModeType(AccessMode) access mode type},
+ * represented as an instance of {@link MethodType}, that serves as a kind of
+ * method signature against which the arguments are checked dynamically.  An
+ * access mode type gives formal parameter types in terms of the coordinate
+ * types of a VarHandle instance and the types for values of importance to the
+ * access mode.  An access mode type also gives a return type, often in terms of
+ * the variable type of a VarHandle instance.  When an access mode method is
+ * invoked on a VarHandle instance, the symbolic type descriptor at the
+ * call site, the run time types of arguments to the invocation, and the run
+ * time type of the return value, must <a href="#invoke">match</a> the types
+ * given in the access mode type.  A runtime exception will be thrown if the
+ * match fails.
+ *
+ * For example, the access mode method {@link #compareAndSet} specifies that if
+ * its receiver is a VarHandle instance with coordinate types
+ * {@code CT1, ..., CTn} and variable type {@code T}, then its access mode type
+ * is {@code (CT1 c1, ..., CTn cn, T expectedValue, T newValue)boolean}.
+ * Suppose that a VarHandle instance can access array elements, and that its
+ * coordinate types are {@code String[]} and {@code int} while its variable type
+ * is {@code String}.  The access mode type for {@code compareAndSet} on this
+ * VarHandle instance would be
+ * {@code (String[] c1, int c2, String expectedValue, String newValue)boolean}.
+ * Such a VarHandle instance may produced by the
+ * {@link MethodHandles#arrayElementVarHandle(Class) array factory method} and
+ * access array elements as follows:
+ * <pre> {@code
+ * String[] sa = ...
+ * VarHandle avh = MethodHandles.arrayElementVarHandle(String[].class);
+ * boolean r = avh.compareAndSet(sa, 10, "expected", "new");
+ * }</pre>
+ *
+ * <p>Access modes control atomicity and consistency properties.
+ * <em>Plain</em> read ({@code get}) and write ({@code set})
+ * accesses are guaranteed to be bitwise atomic only for references
+ * and for primitive values of at most 32 bits, and impose no observable
+ * ordering constraints with respect to threads other than the
+ * executing thread. <em>Opaque</em> operations are bitwise atomic and
+ * coherently ordered with respect to accesses to the same variable.
+ * In addition to obeying Opaque properties, <em>Acquire</em> mode
+ * reads and their subsequent accesses are ordered after matching
+ * <em>Release</em> mode writes and their previous accesses.  In
+ * addition to obeying Acquire and Release properties, all
+ * <em>Volatile</em> operations are totally ordered with respect to
+ * each other.
+ *
+ * <p>Access modes are grouped into the following categories:
+ * <ul>
+ * <li>read access modes that get the value of a variable under specified
+ * memory ordering effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #get get},
+ * {@link #getVolatile getVolatile},
+ * {@link #getAcquire getAcquire},
+ * {@link #getOpaque getOpaque}.
+ * <li>write access modes that set the value of a variable under specified
+ * memory ordering effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #set set},
+ * {@link #setVolatile setVolatile},
+ * {@link #setRelease setRelease},
+ * {@link #setOpaque setOpaque}.
+ * <li>atomic update access modes that, for example, atomically compare and set
+ * the value of a variable under specified memory ordering effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #compareAndSet compareAndSet},
+ * {@link #weakCompareAndSetPlain weakCompareAndSetPlain},
+ * {@link #weakCompareAndSet weakCompareAndSet},
+ * {@link #weakCompareAndSetAcquire weakCompareAndSetAcquire},
+ * {@link #weakCompareAndSetRelease weakCompareAndSetRelease},
+ * {@link #compareAndExchangeAcquire compareAndExchangeAcquire},
+ * {@link #compareAndExchange compareAndExchange},
+ * {@link #compareAndExchangeRelease compareAndExchangeRelease},
+ * {@link #getAndSet getAndSet},
+ * {@link #getAndSetAcquire getAndSetAcquire},
+ * {@link #getAndSetRelease getAndSetRelease}.
+ * <li>numeric atomic update access modes that, for example, atomically get and
+ * set with addition the value of a variable under specified memory ordering
+ * effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #getAndAdd getAndAdd},
+ * {@link #getAndAddAcquire getAndAddAcquire},
+ * {@link #getAndAddRelease getAndAddRelease},
+ * <li>bitwise atomic update access modes that, for example, atomically get and
+ * bitwise OR the value of a variable under specified memory ordering
+ * effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #getAndBitwiseOr getAndBitwiseOr},
+ * {@link #getAndBitwiseOrAcquire getAndBitwiseOrAcquire},
+ * {@link #getAndBitwiseOrRelease getAndBitwiseOrRelease},
+ * {@link #getAndBitwiseAnd getAndBitwiseAnd},
+ * {@link #getAndBitwiseAndAcquire getAndBitwiseAndAcquire},
+ * {@link #getAndBitwiseAndRelease getAndBitwiseAndRelease},
+ * {@link #getAndBitwiseXor getAndBitwiseXor},
+ * {@link #getAndBitwiseXorAcquire getAndBitwiseXorAcquire},
+ * {@link #getAndBitwiseXorRelease getAndBitwiseXorRelease}.
+ * </ul>
+ *
+ * <p>Factory methods that produce or {@link java.lang.invoke.MethodHandles.Lookup
+ * lookup} VarHandle instances document the set of access modes that are
+ * supported, which may also include documenting restrictions based on the
+ * variable type and whether a variable is read-only.  If an access mode is not
+ * supported then the corresponding access mode method will on invocation throw
+ * an {@code UnsupportedOperationException}.  Factory methods should document
+ * any additional undeclared exceptions that may be thrown by access mode
+ * methods.
+ * The {@link #get get} access mode is supported for all
+ * VarHandle instances and the corresponding method never throws
+ * {@code UnsupportedOperationException}.
+ * If a VarHandle references a read-only variable (for example a {@code final}
+ * field) then write, atomic update, numeric atomic update, and bitwise atomic
+ * update access modes are not supported and corresponding methods throw
+ * {@code UnsupportedOperationException}.
+ * Read/write access modes (if supported), with the exception of
+ * {@code get} and {@code set}, provide atomic access for
+ * reference types and all primitive types.
+ * Unless stated otherwise in the documentation of a factory method, the access
+ * modes {@code get} and {@code set} (if supported) provide atomic access for
+ * reference types and all primitives types, with the exception of {@code long}
+ * and {@code double} on 32-bit platforms.
+ *
+ * <p>Access modes will override any memory ordering effects specified at
+ * the declaration site of a variable.  For example, a VarHandle accessing a
+ * a field using the {@code get} access mode will access the field as
+ * specified <em>by its access mode</em> even if that field is declared
+ * {@code volatile}.  When mixed access is performed extreme care should be
+ * taken since the Java Memory Model may permit surprising results.
+ *
+ * <p>In addition to supporting access to variables under various access modes,
+ * a set of static methods, referred to as memory fence methods, is also
+ * provided for fine-grained control of memory ordering.
+ *
+ * The Java Language Specification permits other threads to observe operations
+ * as if they were executed in orders different than are apparent in program
+ * source code, subject to constraints arising, for example, from the use of
+ * locks, {@code volatile} fields or VarHandles.  The static methods,
+ * {@link #fullFence fullFence}, {@link #acquireFence acquireFence},
+ * {@link #releaseFence releaseFence}, {@link #loadLoadFence loadLoadFence} and
+ * {@link #storeStoreFence storeStoreFence}, can also be used to impose
+ * constraints.  Their specifications, as is the case for certain access modes,
+ * are phrased in terms of the lack of "reorderings" -- observable ordering
+ * effects that might otherwise occur if the fence was not present.  More
+ * precise phrasing of the specification of access mode methods and memory fence
+ * methods may accompany future updates of the Java Language Specification.
+ *
+ * <h1>Compiling invocation of access mode methods</h1>
+ * A Java method call expression naming an access mode method can invoke a
+ * VarHandle from Java source code.  From the viewpoint of source code, these
+ * methods can take any arguments and their polymorphic result (if expressed)
+ * can be cast to any return type.  Formally this is accomplished by giving the
+ * access mode methods variable arity {@code Object} arguments and
+ * {@code Object} return types (if the return type is polymorphic), 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 access mode methods
+ * 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 generate
+ * instructions to push them on the stack according to their own unconverted
+ * types.  The VarHandle object itself will be pushed on the stack before the
+ * arguments.  The compiler then generates an {@code invokevirtual} instruction
+ * that invokes the access mode method 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 (if polymorphic).  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><a id="invoke">Performing invocation of access mode methods</a></h1>
+ * The first time an {@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 also holds for calls to access mode
+ * methods.  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 an access mode method 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
+ * VarHandle's access mode 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 access mode method which the caller is
+ * invoking is not present on the individual VarHandle being invoked.
+ *
+ * <p>
+ * Invocation of an access mode method behaves as if an invocation of
+ * {@link MethodHandle#invoke}, where the receiving method handle accepts the
+ * VarHandle instance as the leading argument.  More specifically, the
+ * following, where {@code {access-mode}} corresponds to the access mode method
+ * name:
+ * <pre> {@code
+ * VarHandle vh = ..
+ * R r = (R) vh.{access-mode}(p1, p2, ..., pN);
+ * }</pre>
+ * behaves as if:
+ * <pre> {@code
+ * VarHandle vh = ..
+ * VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
+ * MethodHandle mh = MethodHandles.varHandleExactInvoker(
+ *                       am,
+ *                       vh.accessModeType(am));
+ *
+ * R r = (R) mh.invoke(vh, p1, p2, ..., pN)
+ * }</pre>
+ * (modulo access mode methods do not declare throwing of {@code Throwable}).
+ * This is equivalent to:
+ * <pre> {@code
+ * MethodHandle mh = MethodHandles.lookup().findVirtual(
+ *                       VarHandle.class,
+ *                       "{access-mode}",
+ *                       MethodType.methodType(R, p1, p2, ..., pN));
+ *
+ * R r = (R) mh.invokeExact(vh, p1, p2, ..., pN)
+ * }</pre>
+ * where the desired method type is the symbolic type descriptor and a
+ * {@link MethodHandle#invokeExact} is performed, since before invocation of the
+ * target, the handle will apply reference casts as necessary and box, unbox, or
+ * widen primitive values, as if by {@link MethodHandle#asType asType} (see also
+ * {@link MethodHandles#varHandleInvoker}).
+ *
+ * More concisely, such behaviour is equivalent to:
+ * <pre> {@code
+ * VarHandle vh = ..
+ * VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
+ * MethodHandle mh = vh.toMethodHandle(am);
+ *
+ * R r = (R) mh.invoke(p1, p2, ..., pN)
+ * }</pre>
+ * Where, in this case, the method handle is bound to the VarHandle instance.
+ *
+ *
+ * <h1>Invocation checking</h1>
+ * In typical programs, VarHandle access mode type matching will usually
+ * succeed.  But if a match fails, the JVM will throw a
+ * {@link WrongMethodTypeException}.
+ * <p>
+ * Thus, an access mode 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 VarHandles.
+ * <p>
+ * Because access mode types contain "live" {@code Class} objects, method type
+ * matching takes into account both type names and class loaders.
+ * Thus, even if a VarHandle {@code VH} is created in one class loader
+ * {@code L1} and used in another {@code L2}, VarHandle access mode method
+ * 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 VH} is created and its access mode types are
+ * assigned, while the resolution in {@code L2} happens when the
+ * {@code invokevirtual} instruction is linked.
+ * <p>
+ * Apart from type descriptor checks, a VarHandles's capability to
+ * access it's variables is unrestricted.
+ * If a VarHandle is formed on a non-public variable by a class that has access
+ * to that variable, the resulting VarHandle 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, VarHandle access checking is performed
+ * <a href="MethodHandles.Lookup.html#access">when the VarHandle is
+ * created</a>.
+ * Thus, VarHandles to non-public variables, or to variables 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>VarHandle creation</h1>
+ * Java code can create a VarHandle that directly accesses any 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 VarHandle for a non-static field can be obtained
+ * from {@link java.lang.invoke.MethodHandles.Lookup#findVarHandle
+ * Lookup.findVarHandle}.
+ * There is also a conversion method from Core Reflection API objects,
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflectVarHandle
+ * Lookup.unreflectVarHandle}.
+ * <p>
+ * Access to protected field 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 VarHandle refers to a protected non-static field of a declaring
+ * class outside the current package, the receiver argument will be narrowed to
+ * the type of the accessing class.
+ *
+ * <h1>Interoperation between VarHandles and the Core Reflection API</h1>
+ * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup
+ * Lookup} API, any field represented by a Core Reflection API object
+ * can be converted to a behaviorally equivalent VarHandle.
+ * For example, a reflective {@link java.lang.reflect.Field Field} can
+ * be converted to a VarHandle using
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflectVarHandle
+ * Lookup.unreflectVarHandle}.
+ * The resulting VarHandles generally provide more direct and efficient
+ * access to the underlying fields.
+ * <p>
+ * As a special case, when the Core Reflection API is used to view the
+ * signature polymorphic access mode methods 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
+ * 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}.
+ * However, such reflective calls do not result in access mode method
+ * 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 VarHandle
+ * access mode methods under any symbolic type descriptor, this reflective view
+ * conflicts with the normal presentation of these methods via bytecodes.
+ * Thus, these 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 access mode type,
+ * use {@link java.lang.invoke.MethodHandles#varHandleExactInvoker} or
+ * {@link java.lang.invoke.MethodHandles#varHandleInvoker}.  The
+ * {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual}
+ * API is also able to return a method handle to call an access mode method for
+ * any specified access mode type and is equivalent in behaviour to
+ * {@link java.lang.invoke.MethodHandles#varHandleInvoker}.
+ *
+ * <h1>Interoperation between VarHandles and Java generics</h1>
+ * A VarHandle can be obtained for a variable, such as a a field, which is
+ * declared with Java generic types.  As with the Core Reflection API, the
+ * VarHandle's variable type will be constructed from the erasure of the
+ * source-level type.  When a VarHandle access mode method 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.
+ *
+ * @see MethodHandle
+ * @see MethodHandles
+ * @see MethodType
+ * @since 9
+ * @hide
+ */
+public abstract class VarHandle {
+    // Android-added: Using sun.misc.Unsafe for fence implementation.
+    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
+
+    // BEGIN Android-removed: No VarForm in Android implementation.
+    /*
+    final VarForm vform;
+
+    VarHandle(VarForm vform) {
+        this.vform = vform;
+    }
+    */
+    // END Android-removed: No VarForm in Android implementation.
+
+    RuntimeException unsupported() {
+        return new UnsupportedOperationException();
+    }
+
+    // Plain accessors
+
+    /**
+     * Returns the value of a variable, with memory semantics of reading as
+     * if the variable was declared non-{@code volatile}.  Commonly referred to
+     * as plain read access.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code get}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET)} on this VarHandle.
+     *
+     * <p>This access mode is supported by all VarHandle instances and never
+     * throws {@code UnsupportedOperationException}.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the value of the
+     * variable
+     * , statically represented using {@code Object}.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object get(Object... args);
+
+    /**
+     * Sets the value of a variable to the {@code newValue}, with memory
+     * semantics of setting as if the variable was declared non-{@code volatile}
+     * and non-{@code final}.  Commonly referred to as plain write access.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}
+     *
+     * <p>The symbolic type descriptor at the call site of {@code set}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.SET)} on this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+     * , statically represented using varargs.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    void set(Object... args);
+
+
+    // Volatile accessors
+
+    /**
+     * Returns the value of a variable, with memory semantics of reading as if
+     * the variable was declared {@code volatile}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getVolatile}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_VOLATILE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the value of the
+     * variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getVolatile(Object... args);
+
+    /**
+     * Sets the value of a variable to the {@code newValue}, with memory
+     * semantics of setting as if the variable was declared {@code volatile}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code setVolatile}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.SET_VOLATILE)} on this
+     * VarHandle.
+     *
+     * @apiNote
+     * Ignoring the many semantic differences from C and C++, this method has
+     * memory ordering effects compatible with {@code memory_order_seq_cst}.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+     * , statically represented using varargs.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    void setVolatile(Object... args);
+
+
+    /**
+     * Returns the value of a variable, accessed in program order, but with no
+     * assurance of memory ordering effects with respect to other threads.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getOpaque}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_OPAQUE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the value of the
+     * variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getOpaque(Object... args);
+
+    /**
+     * Sets the value of a variable to the {@code newValue}, in program order,
+     * but with no assurance of memory ordering effects with respect to other
+     * threads.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code setOpaque}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.SET_OPAQUE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+     * , statically represented using varargs.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    void setOpaque(Object... args);
+
+
+    // Lazy accessors
+
+    /**
+     * Returns the value of a variable, and ensures that subsequent loads and
+     * stores are not reordered before this access.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_ACQUIRE)} on this
+     * VarHandle.
+     *
+     * @apiNote
+     * Ignoring the many semantic differences from C and C++, this method has
+     * memory ordering effects compatible with {@code memory_order_acquire}
+     * ordering.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the value of the
+     * variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAcquire(Object... args);
+
+    /**
+     * Sets the value of a variable to the {@code newValue}, and ensures that
+     * prior loads and stores are not reordered after this access.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code setRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.SET_RELEASE)} on this
+     * VarHandle.
+     *
+     * @apiNote
+     * Ignoring the many semantic differences from C and C++, this method has
+     * memory ordering effects compatible with {@code memory_order_release}
+     * ordering.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+     * , statically represented using varargs.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    void setRelease(Object... args);
+
+
+    // Compare and set accessors
+
+    /**
+     * Atomically sets the value of a variable to the {@code newValue} with the
+     * memory semantics of {@link #setVolatile} if the variable's current value,
+     * referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * compareAndSet} must match the access mode type that is the result of
+     * calling {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_SET)} on
+     * this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return {@code true} if successful, otherwise {@code false} if the
+     * witness value was not the same as the {@code expectedValue}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    boolean compareAndSet(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the {@code newValue} with the
+     * memory semantics of {@link #setVolatile} if the variable's current value,
+     * referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * compareAndExchange}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_EXCHANGE)}
+     * on this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the witness value, which
+     * will be the same as the {@code expectedValue} if successful
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type is not
+     * compatible with the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type is compatible with the
+     * caller's symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object compareAndExchange(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the {@code newValue} with the
+     * memory semantics of {@link #set} if the variable's current value,
+     * referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #getAcquire}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * compareAndExchangeAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_EXCHANGE_ACQUIRE)} on
+     * this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the witness value, which
+     * will be the same as the {@code expectedValue} if successful
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #set(Object...)
+     * @see #getAcquire(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object compareAndExchangeAcquire(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the {@code newValue} with the
+     * memory semantics of {@link #setRelease} if the variable's current value,
+     * referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * compareAndExchangeRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_EXCHANGE_RELEASE)}
+     * on this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the witness value, which
+     * will be the same as the {@code expectedValue} if successful
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setRelease(Object...)
+     * @see #get(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object compareAndExchangeRelease(Object... args);
+
+    // Weak (spurious failures allowed)
+
+    /**
+     * Possibly atomically sets the value of a variable to the {@code newValue}
+     * with the semantics of {@link #set} if the variable's current value,
+     * referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>This operation may fail spuriously (typically, due to memory
+     * contention) even if the witness value does match the expected value.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * weakCompareAndSetPlain} must match the access mode type that is the result of
+     * calling {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_PLAIN)}
+     * on this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return {@code true} if successful, otherwise {@code false} if the
+     * witness value was not the same as the {@code expectedValue} or if this
+     * operation spuriously failed.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #set(Object...)
+     * @see #get(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    boolean weakCompareAndSetPlain(Object... args);
+
+    /**
+     * Possibly atomically sets the value of a variable to the {@code newValue}
+     * with the memory semantics of {@link #setVolatile} if the variable's
+     * current value, referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>This operation may fail spuriously (typically, due to memory
+     * contention) even if the witness value does match the expected value.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * weakCompareAndSet} must match the access mode type that is the
+     * result of calling {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET)}
+     * on this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return {@code true} if successful, otherwise {@code false} if the
+     * witness value was not the same as the {@code expectedValue} or if this
+     * operation spuriously failed.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    boolean weakCompareAndSet(Object... args);
+
+    /**
+     * Possibly atomically sets the value of a variable to the {@code newValue}
+     * with the semantics of {@link #set} if the variable's current value,
+     * referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #getAcquire}.
+     *
+     * <p>This operation may fail spuriously (typically, due to memory
+     * contention) even if the witness value does match the expected value.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * weakCompareAndSetAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_ACQUIRE)}
+     * on this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return {@code true} if successful, otherwise {@code false} if the
+     * witness value was not the same as the {@code expectedValue} or if this
+     * operation spuriously failed.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #set(Object...)
+     * @see #getAcquire(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    boolean weakCompareAndSetAcquire(Object... args);
+
+    /**
+     * Possibly atomically sets the value of a variable to the {@code newValue}
+     * with the semantics of {@link #setRelease} if the variable's current
+     * value, referred to as the <em>witness value</em>, {@code ==} the
+     * {@code expectedValue}, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>This operation may fail spuriously (typically, due to memory
+     * contention) even if the witness value does match the expected value.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code
+     * weakCompareAndSetRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_RELEASE)}
+     * on this VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+     * , statically represented using varargs.
+     * @return {@code true} if successful, otherwise {@code false} if the
+     * witness value was not the same as the {@code expectedValue} or if this
+     * operation spuriously failed.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setRelease(Object...)
+     * @see #get(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    boolean weakCompareAndSetRelease(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the {@code newValue} with the
+     * memory semantics of {@link #setVolatile} and returns the variable's
+     * previous value, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndSet}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_SET)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndSet(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the {@code newValue} with the
+     * memory semantics of {@link #set} and returns the variable's
+     * previous value, as accessed with the memory semantics of
+     * {@link #getAcquire}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndSetAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_SET_ACQUIRE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndSetAcquire(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the {@code newValue} with the
+     * memory semantics of {@link #setRelease} and returns the variable's
+     * previous value, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndSetRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_SET_RELEASE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndSetRelease(Object... args);
+
+    // Primitive adders
+    // Throw UnsupportedOperationException for refs
+
+    /**
+     * Atomically adds the {@code value} to the current value of a variable with
+     * the memory semantics of {@link #setVolatile}, and returns the variable's
+     * previous value, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T value)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndAdd}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_ADD)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T value)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndAdd(Object... args);
+
+    /**
+     * Atomically adds the {@code value} to the current value of a variable with
+     * the memory semantics of {@link #set}, and returns the variable's
+     * previous value, as accessed with the memory semantics of
+     * {@link #getAcquire}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T value)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndAddAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_ADD_ACQUIRE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T value)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndAddAcquire(Object... args);
+
+    /**
+     * Atomically adds the {@code value} to the current value of a variable with
+     * the memory semantics of {@link #setRelease}, and returns the variable's
+     * previous value, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T value)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndAddRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_ADD_RELEASE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T value)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndAddRelease(Object... args);
+
+
+    // Bitwise operations
+    // Throw UnsupportedOperationException for refs
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise OR between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #setVolatile} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical OR is performed instead of a bitwise OR.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseOr}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_OR)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseOr(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise OR between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #set} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #getAcquire}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical OR is performed instead of a bitwise OR.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseOrAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_OR_ACQUIRE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #set(Object...)
+     * @see #getAcquire(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseOrAcquire(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise OR between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #setRelease} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical OR is performed instead of a bitwise OR.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseOrRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_OR_RELEASE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setRelease(Object...)
+     * @see #get(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseOrRelease(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise AND between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #setVolatile} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical AND is performed instead of a bitwise AND.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseAnd}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_AND)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseAnd(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise AND between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #set} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #getAcquire}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical AND is performed instead of a bitwise AND.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseAndAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_AND_ACQUIRE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #set(Object...)
+     * @see #getAcquire(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseAndAcquire(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise AND between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #setRelease} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical AND is performed instead of a bitwise AND.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseAndRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_AND_RELEASE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setRelease(Object...)
+     * @see #get(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseAndRelease(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise XOR between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #setVolatile} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #getVolatile}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical XOR is performed instead of a bitwise XOR.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseXor}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_XOR)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setVolatile(Object...)
+     * @see #getVolatile(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseXor(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise XOR between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #set} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #getAcquire}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical XOR is performed instead of a bitwise XOR.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseXorAcquire}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_XOR_ACQUIRE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #set(Object...)
+     * @see #getAcquire(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseXorAcquire(Object... args);
+
+    /**
+     * Atomically sets the value of a variable to the result of
+     * bitwise XOR between the variable's current value and the {@code mask}
+     * with the memory semantics of {@link #setRelease} and returns the
+     * variable's previous value, as accessed with the memory semantics of
+     * {@link #get}.
+     *
+     * <p>If the variable type is the non-integral {@code boolean} type then a
+     * logical XOR is performed instead of a bitwise XOR.
+     *
+     * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+     *
+     * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseXorRelease}
+     * must match the access mode type that is the result of calling
+     * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_XOR_RELEASE)} on this
+     * VarHandle.
+     *
+     * @param args the signature-polymorphic parameter list of the form
+     * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+     * , statically represented using varargs.
+     * @return the signature-polymorphic result that is the previous value of
+     * the variable
+     * , statically represented using {@code Object}.
+     * @throws UnsupportedOperationException if the access mode is unsupported
+     * for this VarHandle.
+     * @throws WrongMethodTypeException if the access mode type does not
+     * match the caller's symbolic type descriptor.
+     * @throws ClassCastException if the access mode type matches the caller's
+     * symbolic type descriptor, but a reference cast fails.
+     * @see #setRelease(Object...)
+     * @see #get(Object...)
+     */
+    public final native
+    // Android-removed: unsupported annotations.
+    // @MethodHandle.PolymorphicSignature
+    // @HotSpotIntrinsicCandidate
+    Object getAndBitwiseXorRelease(Object... args);
+
+
+    enum AccessType {
+        GET(Object.class),
+        SET(void.class),
+        COMPARE_AND_SWAP(boolean.class),
+        COMPARE_AND_EXCHANGE(Object.class),
+        GET_AND_UPDATE(Object.class);
+
+        final Class<?> returnType;
+        final boolean isMonomorphicInReturnType;
+
+        AccessType(Class<?> returnType) {
+            this.returnType = returnType;
+            isMonomorphicInReturnType = returnType != Object.class;
+        }
+
+        MethodType accessModeType(Class<?> receiver, Class<?> value,
+                                  Class<?>... intermediate) {
+            Class<?>[] ps;
+            int i;
+            switch (this) {
+                case GET:
+                    ps = allocateParameters(0, receiver, intermediate);
+                    fillParameters(ps, receiver, intermediate);
+                    return MethodType.methodType(value, ps);
+                case SET:
+                    ps = allocateParameters(1, receiver, intermediate);
+                    i = fillParameters(ps, receiver, intermediate);
+                    ps[i] = value;
+                    return MethodType.methodType(void.class, ps);
+                case COMPARE_AND_SWAP:
+                    ps = allocateParameters(2, receiver, intermediate);
+                    i = fillParameters(ps, receiver, intermediate);
+                    ps[i++] = value;
+                    ps[i] = value;
+                    return MethodType.methodType(boolean.class, ps);
+                case COMPARE_AND_EXCHANGE:
+                    ps = allocateParameters(2, receiver, intermediate);
+                    i = fillParameters(ps, receiver, intermediate);
+                    ps[i++] = value;
+                    ps[i] = value;
+                    return MethodType.methodType(value, ps);
+                case GET_AND_UPDATE:
+                    ps = allocateParameters(1, receiver, intermediate);
+                    i = fillParameters(ps, receiver, intermediate);
+                    ps[i] = value;
+                    return MethodType.methodType(value, ps);
+                default:
+                    throw new InternalError("Unknown AccessType");
+            }
+        }
+
+        private static Class<?>[] allocateParameters(int values,
+                                                     Class<?> receiver, Class<?>... intermediate) {
+            int size = ((receiver != null) ? 1 : 0) + intermediate.length + values;
+            return new Class<?>[size];
+        }
+
+        private static int fillParameters(Class<?>[] ps,
+                                          Class<?> receiver, Class<?>... intermediate) {
+            int i = 0;
+            if (receiver != null)
+                ps[i++] = receiver;
+            for (int j = 0; j < intermediate.length; j++)
+                ps[i++] = intermediate[j];
+            return i;
+        }
+    }
+
+    /**
+     * The set of access modes that specify how a variable, referenced by a
+     * VarHandle, is accessed.
+     */
+    public enum AccessMode {
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#get VarHandle.get}
+         */
+        GET("get", AccessType.GET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#set VarHandle.set}
+         */
+        SET("set", AccessType.SET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#getVolatile VarHandle.getVolatile}
+         */
+        GET_VOLATILE("getVolatile", AccessType.GET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#setVolatile VarHandle.setVolatile}
+         */
+        SET_VOLATILE("setVolatile", AccessType.SET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#getAcquire VarHandle.getAcquire}
+         */
+        GET_ACQUIRE("getAcquire", AccessType.GET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#setRelease VarHandle.setRelease}
+         */
+        SET_RELEASE("setRelease", AccessType.SET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#getOpaque VarHandle.getOpaque}
+         */
+        GET_OPAQUE("getOpaque", AccessType.GET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#setOpaque VarHandle.setOpaque}
+         */
+        SET_OPAQUE("setOpaque", AccessType.SET),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#compareAndSet VarHandle.compareAndSet}
+         */
+        COMPARE_AND_SET("compareAndSet", AccessType.COMPARE_AND_SWAP),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#compareAndExchange VarHandle.compareAndExchange}
+         */
+        COMPARE_AND_EXCHANGE("compareAndExchange", AccessType.COMPARE_AND_EXCHANGE),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#compareAndExchangeAcquire VarHandle.compareAndExchangeAcquire}
+         */
+        COMPARE_AND_EXCHANGE_ACQUIRE("compareAndExchangeAcquire", AccessType.COMPARE_AND_EXCHANGE),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#compareAndExchangeRelease VarHandle.compareAndExchangeRelease}
+         */
+        COMPARE_AND_EXCHANGE_RELEASE("compareAndExchangeRelease", AccessType.COMPARE_AND_EXCHANGE),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#weakCompareAndSetPlain VarHandle.weakCompareAndSetPlain}
+         */
+        WEAK_COMPARE_AND_SET_PLAIN("weakCompareAndSetPlain", AccessType.COMPARE_AND_SWAP),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#weakCompareAndSet VarHandle.weakCompareAndSet}
+         */
+        WEAK_COMPARE_AND_SET("weakCompareAndSet", AccessType.COMPARE_AND_SWAP),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#weakCompareAndSetAcquire VarHandle.weakCompareAndSetAcquire}
+         */
+        WEAK_COMPARE_AND_SET_ACQUIRE("weakCompareAndSetAcquire", AccessType.COMPARE_AND_SWAP),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#weakCompareAndSetRelease VarHandle.weakCompareAndSetRelease}
+         */
+        WEAK_COMPARE_AND_SET_RELEASE("weakCompareAndSetRelease", AccessType.COMPARE_AND_SWAP),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#getAndSet VarHandle.getAndSet}
+         */
+        GET_AND_SET("getAndSet", AccessType.GET_AND_UPDATE),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#getAndSetAcquire VarHandle.getAndSetAcquire}
+         */
+        GET_AND_SET_ACQUIRE("getAndSetAcquire", AccessType.GET_AND_UPDATE),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#getAndSetRelease VarHandle.getAndSetRelease}
+         */
+        GET_AND_SET_RELEASE("getAndSetRelease", AccessType.GET_AND_UPDATE),
+        /**
+         * The access mode whose access is specified by the corresponding
+         * method
+         * {@link VarHandle#getAndAdd VarHandle.getAndAdd}
+         */
+        GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        /**
+         * 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),
+        ;
+
+        static final Map<String, AccessMode> methodNameToAccessMode;
+        static {
+            // Initial capacity of # values is sufficient to avoid resizes
+            // for the smallest table size (32)
+            methodNameToAccessMode = new HashMap<>(AccessMode.values().length);
+            for (AccessMode am : AccessMode.values()) {
+                methodNameToAccessMode.put(am.methodName, am);
+            }
+        }
+
+        final String methodName;
+        final AccessType at;
+
+        AccessMode(final String methodName, AccessType at) {
+            this.methodName = methodName;
+            this.at = at;
+        }
+
+        /**
+         * Returns the {@code VarHandle} signature-polymorphic method name
+         * associated with this {@code AccessMode} value.
+         *
+         * @return the signature-polymorphic method name
+         * @see #valueFromMethodName
+         */
+        public String methodName() {
+            return methodName;
+        }
+
+        /**
+         * Returns the {@code AccessMode} value associated with the specified
+         * {@code VarHandle} signature-polymorphic method name.
+         *
+         * @param methodName the signature-polymorphic method name
+         * @return the {@code AccessMode} value
+         * @throws IllegalArgumentException if there is no {@code AccessMode}
+         *         value associated with method name (indicating the method
+         *         name does not correspond to a {@code VarHandle}
+         *         signature-polymorphic method name).
+         * @see #methodName
+         */
+        public static AccessMode valueFromMethodName(String methodName) {
+            AccessMode am = methodNameToAccessMode.get(methodName);
+            if (am != null) return am;
+            throw new IllegalArgumentException("No AccessMode value for method name " + methodName);
+        }
+
+        // BEGIN Android-removed: MemberName and VarForm are not used in the Android implementation.
+        /*
+        @ForceInline
+        static MemberName getMemberName(int ordinal, VarForm vform) {
+            return vform.memberName_table[ordinal];
+        }
+        */
+        // END Android-removed: MemberName and VarForm are not used in the Android implementation.
+    }
+
+    // BEGIN Android-removed: AccessDescriptor not used in Android implementation.
+    /*
+    static final class AccessDescriptor {
+        final MethodType symbolicMethodTypeErased;
+        final MethodType symbolicMethodTypeInvoker;
+        final Class<?> returnType;
+        final int type;
+        final int mode;
+
+        public AccessDescriptor(MethodType symbolicMethodType, int type, int mode) {
+            this.symbolicMethodTypeErased = symbolicMethodType.erase();
+            this.symbolicMethodTypeInvoker = symbolicMethodType.insertParameterTypes(0, VarHandle.class);
+            this.returnType = symbolicMethodType.returnType();
+            this.type = type;
+            this.mode = mode;
+        }
+    }
+    */
+    // END Android-removed: AccessDescriptor not used in Android implementation.
+
+    /**
+     * Returns the variable type of variables referenced by this VarHandle.
+     *
+     * @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);
+    }
+
+    /**
+     * Returns the coordinate types for this VarHandle.
+     *
+     * @return the coordinate types for this VarHandle. The returned
+     * list is unmodifiable
+     */
+    public final List<Class<?>> coordinateTypes() {
+        MethodType typeGet = accessModeType(AccessMode.GET);
+        return typeGet.parameterList();
+    }
+
+    /**
+     * Obtains the access mode type for this VarHandle and a given access mode.
+     *
+     * <p>The access mode type's parameter types will consist of a prefix that
+     * is the coordinate types of this VarHandle followed by further
+     * types as defined by the access mode method.
+     * The access mode type's return type is defined by the return type of the
+     * access mode method.
+     *
+     * @param accessMode the access mode, corresponding to the
+     * signature-polymorphic method of the same name
+     * @return the access mode type for the given access mode
+     */
+    public final MethodType accessModeType(AccessMode accessMode) {
+        // BEGIN Android-removed: Relies on internal class that is not part of the
+        // Android implementation.
+        /*
+        TypesAndInvokers tis = getTypesAndInvokers();
+        MethodType mt = tis.methodType_table[accessMode.at.ordinal()];
+        if (mt == null) {
+            mt = tis.methodType_table[accessMode.at.ordinal()] =
+                    accessModeTypeUncached(accessMode);
+        }
+        return mt;
+        */
+        // 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-removed: Not part of the Android implementation.
+    // abstract MethodType accessModeTypeUncached(AccessMode accessMode);
+
+    /**
+     * Returns {@code true} if the given access mode is supported, otherwise
+     * {@code false}.
+     *
+     * <p>The return of a {@code false} value for a given access mode indicates
+     * that an {@code UnsupportedOperationException} is thrown on invocation
+     * of the corresponding access mode method.
+     *
+     * @param accessMode the access mode, corresponding to the
+     * signature-polymorphic method of the same name
+     * @return {@code true} if the given access mode is supported, otherwise
+     * {@code false}.
+     */
+    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;
+    }
+
+    /**
+     * Obtains a method handle bound to this VarHandle and the given access
+     * mode.
+     *
+     * @apiNote This method, for a VarHandle {@code vh} and access mode
+     * {@code {access-mode}}, returns a method handle that is equivalent to
+     * method handle {@code bmh} in the following code (though it may be more
+     * efficient):
+     * <pre>{@code
+     * MethodHandle mh = MethodHandles.varHandleExactInvoker(
+     *                       vh.accessModeType(VarHandle.AccessMode.{access-mode}));
+     *
+     * MethodHandle bmh = mh.bindTo(vh);
+     * }</pre>
+     *
+     * @param accessMode the access mode, corresponding to the
+     * signature-polymorphic method of the same name
+     * @return a method handle bound to this VarHandle and the given access mode
+     */
+    public final MethodHandle toMethodHandle(AccessMode accessMode) {
+        // BEGIN Android-removed: no vform field in Android implementation.
+        /*
+        MemberName mn = AccessMode.getMemberName(accessMode.ordinal(), vform);
+        if (mn != null) {
+            MethodHandle mh = getMethodHandle(accessMode.ordinal());
+            return mh.bindTo(this);
+        }
+        else {
+            // Ensure an UnsupportedOperationException is thrown
+            return MethodHandles.varHandleInvoker(accessMode, accessModeType(accessMode)).
+                    bindTo(this);
+        }
+        */
+        // Android-added: Throw an exception until implemented.
+        unsupported();  // TODO(b/65872996)
+        return null;
+    }
+
+    // BEGIN Android-removed: Not used in Android implementation.
+    /*
+    @Stable
+    TypesAndInvokers typesAndInvokers;
+
+    static class TypesAndInvokers {
+        final @Stable
+        MethodType[] methodType_table =
+                new MethodType[VarHandle.AccessType.values().length];
+
+        final @Stable
+        MethodHandle[] methodHandle_table =
+                new MethodHandle[AccessMode.values().length];
+    }
+
+    @ForceInline
+    private final TypesAndInvokers getTypesAndInvokers() {
+        TypesAndInvokers tis = typesAndInvokers;
+        if (tis == null) {
+            tis = typesAndInvokers = new TypesAndInvokers();
+        }
+        return tis;
+    }
+
+    @ForceInline
+    final MethodHandle getMethodHandle(int mode) {
+        TypesAndInvokers tis = getTypesAndInvokers();
+        MethodHandle mh = tis.methodHandle_table[mode];
+        if (mh == null) {
+            mh = tis.methodHandle_table[mode] = getMethodHandleUncached(mode);
+        }
+        return mh;
+    }
+    private final MethodHandle getMethodHandleUncached(int mode) {
+        MethodType mt = accessModeType(AccessMode.values()[mode]).
+                insertParameterTypes(0, VarHandle.class);
+        MemberName mn = vform.getMemberName(mode);
+        DirectMethodHandle dmh = DirectMethodHandle.make(mn);
+        // Such a method handle must not be publically exposed directly
+        // otherwise it can be cracked, it must be transformed or rebound
+        // before exposure
+        MethodHandle mh = dmh.copyWith(mt, dmh.form);
+        assert mh.type().erase() == mn.getMethodType().erase();
+        return mh;
+    }
+    */
+    // END Android-removed: Not used in Android implementation.
+
+    /*non-public*/
+    // BEGIN Android-removed: No VarForm in Android implementation.
+    /*
+    final void updateVarForm(VarForm newVForm) {
+        if (vform == newVForm) return;
+        UNSAFE.putObject(this, VFORM_OFFSET, newVForm);
+        UNSAFE.fullFence();
+    }
+
+    static final BiFunction<String, List<Integer>, ArrayIndexOutOfBoundsException>
+            AIOOBE_SUPPLIER = Preconditions.outOfBoundsExceptionFormatter(
+            new Function<String, ArrayIndexOutOfBoundsException>() {
+                @Override
+                public ArrayIndexOutOfBoundsException apply(String s) {
+                    return new ArrayIndexOutOfBoundsException(s);
+                }
+            });
+
+    private static final long VFORM_OFFSET;
+
+    static {
+        try {
+            VFORM_OFFSET = UNSAFE.objectFieldOffset(VarHandle.class.getDeclaredField("vform"));
+        }
+        catch (ReflectiveOperationException e) {
+            throw newInternalError(e);
+        }
+
+        // The VarHandleGuards must be initialized to ensure correct
+        // compilation of the guard methods
+        UNSAFE.ensureClassInitialized(VarHandleGuards.class);
+    }
+    */
+    // END Android-removed: No VarForm in Android implementation.
+
+    // Fence methods
+
+    /**
+     * Ensures that loads and stores before the fence will not be reordered
+     * with
+     * loads and stores after the fence.
+     *
+     * @apiNote Ignoring the many semantic differences from C and C++, this
+     * method has memory ordering effects compatible with
+     * {@code atomic_thread_fence(memory_order_seq_cst)}
+     */
+    // Android-removed: @ForceInline is an unsupported attribute.
+    // @ForceInline
+    public static void fullFence() {
+        UNSAFE.fullFence();
+    }
+
+    /**
+     * Ensures that loads before the fence will not be reordered with loads and
+     * stores after the fence.
+     *
+     * @apiNote Ignoring the many semantic differences from C and C++, this
+     * method has memory ordering effects compatible with
+     * {@code atomic_thread_fence(memory_order_acquire)}
+     */
+    // Android-removed: @ForceInline is an unsupported attribute.
+    // @ForceInline
+    public static void acquireFence() {
+        UNSAFE.loadFence();
+    }
+
+    /**
+     * Ensures that loads and stores before the fence will not be
+     * reordered with stores after the fence.
+     *
+     * @apiNote Ignoring the many semantic differences from C and C++, this
+     * method has memory ordering effects compatible with
+     * {@code atomic_thread_fence(memory_order_release)}
+     */
+    // Android-removed: @ForceInline is an unsupported attribute.
+    // @ForceInline
+    public static void releaseFence() {
+        UNSAFE.storeFence();
+    }
+
+    /**
+     * Ensures that loads before the fence will not be reordered with
+     * loads after the fence.
+     */
+    // Android-removed: @ForceInline is an unsupported attribute.
+    // @ForceInline
+    public static void loadLoadFence() {
+        // Android-changed: Not using UNSAFE.loadLoadFence() as not present on Android.
+        // NB The compiler recognizes all the fences here as intrinsics.
+        UNSAFE.loadFence();
+    }
+
+    /**
+     * Ensures that stores before the fence will not be reordered with
+     * stores after the fence.
+     */
+    // Android-removed: @ForceInline is an unsupported attribute.
+    // @ForceInline
+    public static void storeStoreFence() {
+        // Android-changed: Not using UNSAFE.storeStoreFence() as not present on Android.
+        // NB The compiler recognizes all the fences here as intrinsics.
+        UNSAFE.storeFence();
+    }
+}
diff --git a/java/net/Inet6AddressImpl.java b/java/net/Inet6AddressImpl.java
index 2a897f7..cfc2d13 100644
--- a/java/net/Inet6AddressImpl.java
+++ b/java/net/Inet6AddressImpl.java
@@ -48,11 +48,18 @@
 import static android.system.OsConstants.ICMP_ECHOREPLY;
 import static android.system.OsConstants.IPPROTO_ICMP;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_IPV6;
-import static android.system.OsConstants.IPV6_UNICAST_HOPS;
 import static android.system.OsConstants.SOCK_DGRAM;
 import static android.system.OsConstants.SOCK_STREAM;
 
+// Android-note: Android-specific behavior and Linux-based implementation
+// http://b/36933260 Implement root-less ICMP for isReachable()
+// http://b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
+// http://b/25861497 Add BlockGuard checks.
+// http://b/26700324 Fix odd dependency chains of the static InetAddress.
+// anyLocalAddress() Let anyLocalAddress() always return an IPv6 address.
+// Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
+// Rewrote hostname lookup methods on top of Libcore.os. Merge implementation from InetAddress
+//   and remove native methods in this class
 /*
  * Package private implementation of InetAddressImpl for dual
  * IPv4/IPv6 stack. {@code #anyLocalAddress()} will always return an IPv6 address.
@@ -69,6 +76,14 @@
 
     private static final AddressCache addressCache = new AddressCache();
 
+    // BEGIN Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
+    /*
+    public native String getLocalHostName() throws UnknownHostException;
+    public native InetAddress[]
+        lookupAllHostAddr(String hostname) throws UnknownHostException;
+    public native String getHostByAddr(byte[] addr) throws UnknownHostException;
+    private native boolean isReachable0(byte[] addr, int scope, int timeout, byte[] inf, int ttl, int if_scope) throws IOException;
+    */
     @Override
     public InetAddress[] lookupAllHostAddr(String host, int netId) throws UnknownHostException {
         if (host == null || host.isEmpty()) {
@@ -154,10 +169,11 @@
     public void clearAddressCache() {
         addressCache.clear();
     }
+    // END Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
 
     @Override
     public boolean isReachable(InetAddress addr, int timeout, NetworkInterface netif, int ttl) throws IOException {
-        // Android-changed: rewritten on the top of IoBridge and Libcore.os
+        // Android-changed: rewritten on the top of IoBridge and Libcore.os.
         InetAddress sourceAddr = null;
         if (netif != null) {
             /*
@@ -183,6 +199,12 @@
             }
         }
 
+        // Android-changed: http://b/36933260 Implement root-less ICMP for isReachable().
+        /*
+        if (addr instanceof Inet6Address)
+            scope = ((Inet6Address) addr).getScopeId();
+        return isReachable0(addr.getAddress(), scope, timeout, ifaddr, ttl, netif_scope);
+        */
         // Try ICMP first
         if (icmpEcho(addr, timeout, sourceAddr, ttl)) {
             return true;
@@ -192,6 +214,7 @@
         return tcpEcho(addr, timeout, sourceAddr, ttl);
     }
 
+    // BEGIN Android-added: http://b/36933260 Implement root-less ICMP for isReachable().
     private boolean tcpEcho(InetAddress addr, int timeout, InetAddress sourceAddr, int ttl)
             throws IOException {
         FileDescriptor fd = null;
@@ -274,14 +297,16 @@
 
         return false;
     }
+    // END Android-added: http://b/36933260 Implement root-less ICMP for isReachable().
 
+    // BEGIN Android-changed: Let anyLocalAddress() always return an IPv6 address.
     @Override
     public InetAddress anyLocalAddress() {
         synchronized (Inet6AddressImpl.class) {
             // We avoid initializing anyLocalAddress during <clinit> to avoid issues
             // caused by the dependency chains of these classes. InetAddress depends on
             // InetAddressImpl, but Inet6Address & Inet4Address are its subclasses.
-            // Also see {@code loopbackAddresses).
+            // Also see {@code loopbackAddresses). http://b/26700324
             if (anyLocalAddress == null) {
                 Inet6Address anyAddress = new Inet6Address();
                 anyAddress.holder().hostName = "::";
@@ -291,7 +316,9 @@
             return anyLocalAddress;
         }
     }
+    // END Android-changed: Let anyLocalAddress() always return an IPv6 address.
 
+    // BEGIN Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
     @Override
     public InetAddress[] loopbackAddresses() {
         synchronized (Inet6AddressImpl.class) {
@@ -306,7 +333,9 @@
             return loopbackAddresses;
         }
     }
+    // END Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
 
+    // BEGIN Android-changed: b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
     private String getHostByAddr0(byte[] addr) throws UnknownHostException {
         // Android-changed: Rewritten on the top of Libcore.os
         InetAddress hostaddr = InetAddress.getByAddress(addr);
@@ -318,4 +347,5 @@
             throw uhe;
         }
     }
+    // END Android-changed: b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
 }
diff --git a/java/net/InetAddress.java b/java/net/InetAddress.java
index 1468b2d..40b46d2 100644
--- a/java/net/InetAddress.java
+++ b/java/net/InetAddress.java
@@ -181,6 +181,24 @@
  */
 public
 class InetAddress implements java.io.Serializable {
+    // BEGIN Android-removed: Android uses linux-based OsConstants.
+    /*
+     * Specify the address family: Internet Protocol, Version 4
+     * @since 1.4
+     *
+    static final int IPv4 = 1;
+
+    /**
+     * Specify the address family: Internet Protocol, Version 6
+     * @since 1.4
+     *
+    static final int IPv6 = 2;
+    */
+    // END Android-removed: Android uses linux-based OsConstants.
+
+    // Android-removed: Android doesn't support the preference.
+    // /* Specify address family preference */
+    //static transient boolean preferIPv6Address = false;
 
     static class InetAddressHolder {
         /**
@@ -235,6 +253,7 @@
             return address;
         }
 
+        // Android-changed: Documentation: use Linux-based OsConstants.
         /**
          * Specifies the address family type, for instance, AF_INET for IPv4
          * addresses, and AF_INET6 for IPv6 addresses.
@@ -256,6 +275,9 @@
     static final InetAddressImpl impl = new Inet6AddressImpl();
 
     /* Used to store the name service provider */
+    // Android-changed: Android has only one name service.
+    // Android doesn't allow user to provide custom name services.
+    // private static List<NameService> nameServices = null;
     private static final NameService nameService = new NameService() {
         public InetAddress[] lookupAllHostAddr(String host, int netId)
                 throws UnknownHostException {
@@ -273,6 +295,26 @@
     /** use serialVersionUID from JDK 1.0.2 for interoperability */
     private static final long serialVersionUID = 3286316764910316507L;
 
+
+    // BEGIN Android-removed: Android doesn't need to load native library.
+    /*
+     * Load net library into runtime, and perform initializations.
+     *
+    static {
+        preferIPv6Address = java.security.AccessController.doPrivileged(
+            new GetBooleanAction("java.net.preferIPv6Addresses")).booleanValue();
+        AccessController.doPrivileged(
+            new java.security.PrivilegedAction<Void>() {
+                public Void run() {
+                    System.loadLibrary("net");
+                    return null;
+                }
+            });
+        init();
+    }
+    */
+    // END Android-removed: Android doesn't need to load native library.
+
     /**
      * Constructor for the Socket.accept() method.
      * This creates an empty InetAddress, which is filled in by
@@ -433,9 +475,12 @@
      * @since 1.5
      */
     public boolean isReachable(int timeout) throws IOException {
-        return isReachable(null, 0, timeout);
+        return isReachable(null, 0 , timeout);
     }
 
+    // Android-changed: Document that impl tries ICMP ECHO REQUESTs first.
+    // The sole implementation, Inet6AddressImpl.isReachable(), tries ICMP ECHO REQUESTs before
+    // TCP ECHO REQUESTs on Android. On Android, these are both possible without root access.
     /**
      * Test whether that address is reachable. Best effort is made by the
      * implementation to try to reach the host, but firewalls and server
@@ -478,12 +523,14 @@
         return impl.isReachable(this, timeout, netif, ttl);
     }
 
+    // BEGIN Android-added: isReachableByICMP(timeout).
     /**
      * @hide For testing only
      */
     public boolean isReachableByICMP(int timeout) throws IOException {
         return ((Inet6AddressImpl) impl).icmpEcho(this, timeout, null, 0);
     }
+    // END Android-added: isReachableByICMP(timeout).
 
     /**
      * Gets the host name for this IP address.
@@ -511,12 +558,46 @@
      * @see SecurityManager#checkConnect
      */
     public String getHostName() {
+        // Android-changed: Remove SecurityManager check.
         if (holder().getHostName() == null) {
             holder().hostName = InetAddress.getHostFromNameService(this);
         }
         return holder().getHostName();
     }
 
+    // BEGIN Android-removed: Android doesn't support SecurityManager.
+    /*
+     * Returns the hostname for this address.
+     * If the host is equal to null, then this address refers to any
+     * of the local machine's available network addresses.
+     * this is package private so SocketPermission can make calls into
+     * here without a security check.
+     *
+     * <p>If there is a security manager, this method first
+     * calls its {@code checkConnect} method
+     * with the hostname and {@code -1}
+     * as its arguments to see if the calling code is allowed to know
+     * the hostname for this IP address, i.e., to connect to the host.
+     * If the operation is not allowed, it will return
+     * the textual representation of the IP address.
+     *
+     * @return  the host name for this IP address, or if the operation
+     *    is not allowed by the security check, the textual
+     *    representation of the IP address.
+     *
+     * @param check make security check if true
+     *
+     * @see SecurityManager#checkConnect
+     *
+    String getHostName(boolean check) {
+        if (holder().getHostName() == null) {
+            holder().hostName = InetAddress.getHostFromNameService(this, check);
+        }
+        return holder().getHostName();
+    }
+    */
+    // END Android-removed: Android doesn't support SecurityManager.
+
     /**
      * Gets the fully qualified domain name for this IP address.
      * Best effort method, meaning we may not be able to return
@@ -539,12 +620,15 @@
      * @since 1.4
      */
     public String getCanonicalHostName() {
+        // Android-changed: Remove SecurityManager check.
         if (canonicalHostName == null) {
             canonicalHostName = InetAddress.getHostFromNameService(this);
         }
         return canonicalHostName;
     }
 
+    // Android-changed: Remove SecurityManager check.
+    // * @param check make security check if true
     /**
      * Returns the hostname for this address.
      *
@@ -566,6 +650,7 @@
         String host = null;
         try {
             // first lookup the hostname
+            // Android-changed: Android has only one name service.
             host = nameService.getHostByAddr(addr.getAddress());
 
                 /* now get all the IP addresses for this hostname,
@@ -659,6 +744,280 @@
             + "/" + getHostAddress();
     }
 
+    // BEGIN Android-removed: Resolves a hostname using Libcore.os.
+    /*
+     * Cached addresses - our own litle nis, not!
+     *
+    private static Cache addressCache = new Cache(Cache.Type.Positive);
+
+    private static Cache negativeCache = new Cache(Cache.Type.Negative);
+
+    private static boolean addressCacheInit = false;
+
+    static InetAddress[]    unknown_array; // put THIS in cache
+
+    static InetAddressImpl  impl;
+
+    private static final HashMap<String, Void> lookupTable = new HashMap<>();
+
+    /**
+     * Represents a cache entry
+     *
+    static final class CacheEntry {
+
+        CacheEntry(InetAddress[] addresses, long expiration) {
+            this.addresses = addresses;
+            this.expiration = expiration;
+        }
+
+        InetAddress[] addresses;
+        long expiration;
+    }
+
+    /**
+     * A cache that manages entries based on a policy specified
+     * at creation time.
+     *
+    static final class Cache {
+        private LinkedHashMap<String, CacheEntry> cache;
+        private Type type;
+
+        enum Type {Positive, Negative};
+
+        /**
+         * Create cache
+         *
+        public Cache(Type type) {
+            this.type = type;
+            cache = new LinkedHashMap<String, CacheEntry>();
+        }
+
+        private int getPolicy() {
+            if (type == Type.Positive) {
+                return InetAddressCachePolicy.get();
+            } else {
+                return InetAddressCachePolicy.getNegative();
+            }
+        }
+
+        /**
+         * Add an entry to the cache. If there's already an
+         * entry then for this host then the entry will be
+         * replaced.
+         *
+        public Cache put(String host, InetAddress[] addresses) {
+            int policy = getPolicy();
+            if (policy == InetAddressCachePolicy.NEVER) {
+                return this;
+            }
+
+            // purge any expired entries
+
+            if (policy != InetAddressCachePolicy.FOREVER) {
+
+                // As we iterate in insertion order we can
+                // terminate when a non-expired entry is found.
+                LinkedList<String> expired = new LinkedList<>();
+                long now = System.currentTimeMillis();
+                for (String key : cache.keySet()) {
+                    CacheEntry entry = cache.get(key);
+
+                    if (entry.expiration >= 0 && entry.expiration < now) {
+                        expired.add(key);
+                    } else {
+                        break;
+                    }
+                }
+
+                for (String key : expired) {
+                    cache.remove(key);
+                }
+            }
+
+            // create new entry and add it to the cache
+            // -- as a HashMap replaces existing entries we
+            //    don't need to explicitly check if there is
+            //    already an entry for this host.
+            long expiration;
+            if (policy == InetAddressCachePolicy.FOREVER) {
+                expiration = -1;
+            } else {
+                expiration = System.currentTimeMillis() + (policy * 1000);
+            }
+            CacheEntry entry = new CacheEntry(addresses, expiration);
+            cache.put(host, entry);
+            return this;
+        }
+
+        /**
+         * Query the cache for the specific host. If found then
+         * return its CacheEntry, or null if not found.
+         *
+        public CacheEntry get(String host) {
+            int policy = getPolicy();
+            if (policy == InetAddressCachePolicy.NEVER) {
+                return null;
+            }
+            CacheEntry entry = cache.get(host);
+
+            // check if entry has expired
+            if (entry != null && policy != InetAddressCachePolicy.FOREVER) {
+                if (entry.expiration >= 0 &&
+                        entry.expiration < System.currentTimeMillis()) {
+                    cache.remove(host);
+                    entry = null;
+                }
+            }
+
+            return entry;
+        }
+    }
+
+    /*
+     * Initialize cache and insert anyLocalAddress into the
+     * unknown array with no expiry.
+     *
+    private static void cacheInitIfNeeded() {
+        assert Thread.holdsLock(addressCache);
+        if (addressCacheInit) {
+            return;
+        }
+        unknown_array = new InetAddress[1];
+        unknown_array[0] = impl.anyLocalAddress();
+
+        addressCache.put(impl.anyLocalAddress().getHostName(),
+                         unknown_array);
+
+        addressCacheInit = true;
+    }
+
+    /*
+     * Cache the given hostname and addresses.
+     *
+    private static void cacheAddresses(String hostname,
+                                       InetAddress[] addresses,
+                                       boolean success) {
+        hostname = hostname.toLowerCase();
+        synchronized (addressCache) {
+            cacheInitIfNeeded();
+            if (success) {
+                addressCache.put(hostname, addresses);
+            } else {
+                negativeCache.put(hostname, addresses);
+            }
+        }
+    }
+
+    /*
+     * Lookup hostname in cache (positive & negative cache). If
+     * found return addresses, null if not found.
+     *
+    private static InetAddress[] getCachedAddresses(String hostname) {
+        hostname = hostname.toLowerCase();
+
+        // search both positive & negative caches
+
+        synchronized (addressCache) {
+            cacheInitIfNeeded();
+
+            CacheEntry entry = addressCache.get(hostname);
+            if (entry == null) {
+                entry = negativeCache.get(hostname);
+            }
+
+            if (entry != null) {
+                return entry.addresses;
+            }
+        }
+
+        // not found
+        return null;
+    }
+
+    private static NameService createNSProvider(String provider) {
+        if (provider == null)
+            return null;
+
+        NameService nameService = null;
+        if (provider.equals("default")) {
+            // initialize the default name service
+            nameService = new NameService() {
+                public InetAddress[] lookupAllHostAddr(String host)
+                    throws UnknownHostException {
+                    return impl.lookupAllHostAddr(host);
+                }
+                public String getHostByAddr(byte[] addr)
+                    throws UnknownHostException {
+                    return impl.getHostByAddr(addr);
+                }
+            };
+        } else {
+            final String providerName = provider;
+            try {
+                nameService = java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedExceptionAction<NameService>() {
+                        public NameService run() {
+                            Iterator<NameServiceDescriptor> itr =
+                                ServiceLoader.load(NameServiceDescriptor.class)
+                                    .iterator();
+                            while (itr.hasNext()) {
+                                NameServiceDescriptor nsd = itr.next();
+                                if (providerName.
+                                    equalsIgnoreCase(nsd.getType()+","
+                                        +nsd.getProviderName())) {
+                                    try {
+                                        return nsd.createNameService();
+                                    } catch (Exception e) {
+                                        e.printStackTrace();
+                                        System.err.println(
+                                            "Cannot create name service:"
+                                             +providerName+": " + e);
+                                    }
+                                }
+                            }
+
+                            return null;
+                        }
+                    }
+                );
+            } catch (java.security.PrivilegedActionException e) {
+            }
+        }
+
+        return nameService;
+    }
+
+    static {
+        // create the impl
+        impl = InetAddressImplFactory.create();
+
+        // get name service if provided and requested
+        String provider = null;;
+        String propPrefix = "sun.net.spi.nameservice.provider.";
+        int n = 1;
+        nameServices = new ArrayList<NameService>();
+        provider = AccessController.doPrivileged(
+                new GetPropertyAction(propPrefix + n));
+        while (provider != null) {
+            NameService ns = createNSProvider(provider);
+            if (ns != null)
+                nameServices.add(ns);
+
+            n++;
+            provider = AccessController.doPrivileged(
+                    new GetPropertyAction(propPrefix + n));
+        }
+
+        // if not designate any name services provider,
+        // create a default one
+        if (nameServices.size() == 0) {
+            NameService ns = createNSProvider("default");
+            nameServices.add(ns);
+        }
+    }
+    */
+    // END Android-removed: Resolves a hostname using Libcore.os.
+
     /**
      * Creates an InetAddress based on the provided host name and IP address.
      * No name service is checked for the validity of the address.
@@ -685,6 +1044,7 @@
         return getByAddress(host, addr, -1 /* scopeId */);
     }
 
+    // Android-added: Called by native code in Libcore.io.
     // Do not delete. Called from native code.
     private static InetAddress getByAddress(String host, byte[] addr, int scopeId)
         throws UnknownHostException {
@@ -740,6 +1100,7 @@
      */
     public static InetAddress getByName(String host)
         throws UnknownHostException {
+        // Android-changed: Rewritten on the top of Libcore.os.
         return impl.lookupAllHostAddr(host, NETID_UNSET)[0];
     }
 
@@ -784,6 +1145,8 @@
      */
     public static InetAddress[] getAllByName(String host)
         throws UnknownHostException {
+        // Android-changed: Resolves a hostname using Libcore.os.
+        // Also, returns both the Inet4 and Inet6 loopback for null/empty host
         return impl.lookupAllHostAddr(host, NETID_UNSET).clone();
     }
 
@@ -799,9 +1162,223 @@
      * @since 1.7
      */
     public static InetAddress getLoopbackAddress() {
+        // Android-changed: Always returns IPv6 loopback address in Android.
         return impl.loopbackAddresses()[0];
     }
 
+    // BEGIN Android-removed: Resolves a hostname using Libcore.os.
+    /*
+     * check if the literal address string has %nn appended
+     * returns -1 if not, or the numeric value otherwise.
+     *
+     * %nn may also be a string that represents the displayName of
+     * a currently available NetworkInterface.
+     *
+    private static int checkNumericZone (String s) throws UnknownHostException {
+        int percent = s.indexOf ('%');
+        int slen = s.length();
+        int digit, zone=0;
+        if (percent == -1) {
+            return -1;
+        }
+        for (int i=percent+1; i<slen; i++) {
+            char c = s.charAt(i);
+            if (c == ']') {
+                if (i == percent+1) {
+                    /* empty per-cent field *
+                    return -1;
+                }
+                break;
+            }
+            if ((digit = Character.digit (c, 10)) < 0) {
+                return -1;
+            }
+            zone = (zone * 10) + digit;
+        }
+        return zone;
+    }
+
+    private static InetAddress[] getAllByName0 (String host)
+        throws UnknownHostException
+    {
+        return getAllByName0(host, true);
+    }
+
+    /**
+     * package private so SocketPermission can call it
+     *
+    static InetAddress[] getAllByName0 (String host, boolean check)
+        throws UnknownHostException  {
+        return getAllByName0 (host, null, check);
+    }
+
+    private static InetAddress[] getAllByName0 (String host, InetAddress reqAddr, boolean check)
+        throws UnknownHostException  {
+
+        /* If it gets here it is presumed to be a hostname */
+        /* Cache.get can return: null, unknownAddress, or InetAddress[] */
+
+        /* make sure the connection to the host is allowed, before we
+         * give out a hostname
+         *
+        if (check) {
+            SecurityManager security = System.getSecurityManager();
+            if (security != null) {
+                security.checkConnect(host, -1);
+            }
+        }
+
+        InetAddress[] addresses = getCachedAddresses(host);
+
+        /* If no entry in cache, then do the host lookup *
+        if (addresses == null) {
+            addresses = getAddressesFromNameService(host, reqAddr);
+        }
+
+        if (addresses == unknown_array)
+            throw new UnknownHostException(host);
+
+        return addresses.clone();
+    }
+
+    private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr)
+        throws UnknownHostException
+    {
+        InetAddress[] addresses = null;
+        boolean success = false;
+        UnknownHostException ex = null;
+
+        // Check whether the host is in the lookupTable.
+        // 1) If the host isn't in the lookupTable when
+        //    checkLookupTable() is called, checkLookupTable()
+        //    would add the host in the lookupTable and
+        //    return null. So we will do the lookup.
+        // 2) If the host is in the lookupTable when
+        //    checkLookupTable() is called, the current thread
+        //    would be blocked until the host is removed
+        //    from the lookupTable. Then this thread
+        //    should try to look up the addressCache.
+        //     i) if it found the addresses in the
+        //        addressCache, checkLookupTable()  would
+        //        return the addresses.
+        //     ii) if it didn't find the addresses in the
+        //         addressCache for any reason,
+        //         it should add the host in the
+        //         lookupTable and return null so the
+        //         following code would do  a lookup itself.
+        if ((addresses = checkLookupTable(host)) == null) {
+            try {
+                // This is the first thread which looks up the addresses
+                // this host or the cache entry for this host has been
+                // expired so this thread should do the lookup.
+                for (NameService nameService : nameServices) {
+                    try {
+                        /*
+                         * Do not put the call to lookup() inside the
+                         * constructor.  if you do you will still be
+                         * allocating space when the lookup fails.
+                         *
+
+                        addresses = nameService.lookupAllHostAddr(host);
+                        success = true;
+                        break;
+                    } catch (UnknownHostException uhe) {
+                        if (host.equalsIgnoreCase("localhost")) {
+                            InetAddress[] local = new InetAddress[] { impl.loopbackAddress() };
+                            addresses = local;
+                            success = true;
+                            break;
+                        }
+                        else {
+                            addresses = unknown_array;
+                            success = false;
+                            ex = uhe;
+                        }
+                    }
+                }
+
+                // More to do?
+                if (reqAddr != null && addresses.length > 1 && !addresses[0].equals(reqAddr)) {
+                    // Find it?
+                    int i = 1;
+                    for (; i < addresses.length; i++) {
+                        if (addresses[i].equals(reqAddr)) {
+                            break;
+                        }
+                    }
+                    // Rotate
+                    if (i < addresses.length) {
+                        InetAddress tmp, tmp2 = reqAddr;
+                        for (int j = 0; j < i; j++) {
+                            tmp = addresses[j];
+                            addresses[j] = tmp2;
+                            tmp2 = tmp;
+                        }
+                        addresses[i] = tmp2;
+                    }
+                }
+                // Cache the address.
+                cacheAddresses(host, addresses, success);
+
+                if (!success && ex != null)
+                    throw ex;
+
+            } finally {
+                // Delete host from the lookupTable and notify
+                // all threads waiting on the lookupTable monitor.
+                updateLookupTable(host);
+            }
+        }
+
+        return addresses;
+    }
+
+
+    private static InetAddress[] checkLookupTable(String host) {
+        synchronized (lookupTable) {
+            // If the host isn't in the lookupTable, add it in the
+            // lookuptable and return null. The caller should do
+            // the lookup.
+            if (lookupTable.containsKey(host) == false) {
+                lookupTable.put(host, null);
+                return null;
+            }
+
+            // If the host is in the lookupTable, it means that another
+            // thread is trying to look up the addresses of this host.
+            // This thread should wait.
+            while (lookupTable.containsKey(host)) {
+                try {
+                    lookupTable.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        // The other thread has finished looking up the addresses of
+        // the host. This thread should retry to get the addresses
+        // from the addressCache. If it doesn't get the addresses from
+        // the cache, it will try to look up the addresses itself.
+        InetAddress[] addresses = getCachedAddresses(host);
+        if (addresses == null) {
+            synchronized (lookupTable) {
+                lookupTable.put(host, null);
+                return null;
+            }
+        }
+
+        return addresses;
+    }
+
+    private static void updateLookupTable(String host) {
+        synchronized (lookupTable) {
+            lookupTable.remove(host);
+            lookupTable.notifyAll();
+        }
+    }
+    */
+    // END Android-removed: Resolves a hostname using Libcore.os.
+
     /**
      * Returns an {@code InetAddress} object given the raw IP address .
      * The argument is in network byte order: the highest order
@@ -823,6 +1400,15 @@
         return getByAddress(null, addr);
     }
 
+    // BEGIN Android-removed: Resolves a hostname using Libcore.os.
+    /*
+    private static InetAddress cachedLocalHost = null;
+    private static long cacheTime = 0;
+    private static final long maxCacheTime = 5000L;
+    private static final Object cacheLock = new Object();
+    */
+    // END Android-removed: Resolves a hostname using Libcore.os.
+
     /**
      * Returns the address of the local host. This is achieved by retrieving
      * the name of the host from the system, then resolving that name into
@@ -847,10 +1433,68 @@
      * @see java.net.InetAddress#getByName(java.lang.String)
      */
     public static InetAddress getLocalHost() throws UnknownHostException {
+        // BEGIN Android-changed: Resolves a hostname using Libcore.os.
+        /*
+        SecurityManager security = System.getSecurityManager();
+        try {
+            String local = impl.getLocalHostName();
+
+            if (security != null) {
+                security.checkConnect(local, -1);
+            }
+
+            if (local.equals("localhost")) {
+                return impl.loopbackAddress();
+            }
+
+            InetAddress ret = null;
+            synchronized (cacheLock) {
+                long now = System.currentTimeMillis();
+                if (cachedLocalHost != null) {
+                    if ((now - cacheTime) < maxCacheTime) // Less than 5s old?
+                        ret = cachedLocalHost;
+                    else
+                        cachedLocalHost = null;
+                }
+
+                // we are calling getAddressesFromNameService directly
+                // to avoid getting localHost from cache
+                if (ret == null) {
+                    InetAddress[] localAddrs;
+                    try {
+                        localAddrs =
+                            InetAddress.getAddressesFromNameService(local, null);
+                    } catch (UnknownHostException uhe) {
+                        // Rethrow with a more informative error message.
+                        UnknownHostException uhe2 =
+                            new UnknownHostException(local + ": " +
+                                                     uhe.getMessage());
+                        uhe2.initCause(uhe);
+                        throw uhe2;
+                    }
+                    cachedLocalHost = localAddrs[0];
+                    cacheTime = now;
+                    ret = localAddrs[0];
+                }
+            }
+            return ret;
+        } catch (java.lang.SecurityException e) {
+            return impl.loopbackAddress();
+        }
+        */
         String local = Libcore.os.uname().nodename;
         return impl.lookupAllHostAddr(local, NETID_UNSET)[0];
+        // END Android-changed: Resolves a hostname using Libcore.os.
     }
 
+    // BEGIN Android-removed: Android doesn't need to call native init.
+    /**
+     * Perform class load-time initializations.
+     *
+    private static native void init();
+    */
+    // END Android-removed: Android doesn't need to call native init.
+
     /*
      * Returns the InetAddress representing anyLocalAddress
      * (typically 0.0.0.0 or ::0)
@@ -859,6 +1503,51 @@
         return impl.anyLocalAddress();
     }
 
+    // BEGIN Android-removed: Android doesn't load user-provided implementation.
+    /*
+     * Load and instantiate an underlying impl class
+     *
+    static InetAddressImpl loadImpl(String implName) {
+        Object impl = null;
+
+        /*
+         * Property "impl.prefix" will be prepended to the classname
+         * of the implementation object we instantiate, to which we
+         * delegate the real work (like native methods).  This
+         * property can vary across implementations of the java.
+         * classes.  The default is an empty String "".
+         *
+        String prefix = AccessController.doPrivileged(
+                      new GetPropertyAction("impl.prefix", ""));
+        try {
+            impl = Class.forName("java.net." + prefix + implName).newInstance();
+        } catch (ClassNotFoundException e) {
+            System.err.println("Class not found: java.net." + prefix +
+                               implName + ":\ncheck impl.prefix property " +
+                               "in your properties file.");
+        } catch (InstantiationException e) {
+            System.err.println("Could not instantiate: java.net." + prefix +
+                               implName + ":\ncheck impl.prefix property " +
+                               "in your properties file.");
+        } catch (IllegalAccessException e) {
+            System.err.println("Cannot access class: java.net." + prefix +
+                               implName + ":\ncheck impl.prefix property " +
+                               "in your properties file.");
+        }
+
+        if (impl == null) {
+            try {
+                impl = Class.forName(implName).newInstance();
+            } catch (Exception e) {
+                throw new Error("System property impl.prefix incorrect");
+            }
+        }
+
+        return (InetAddressImpl) impl;
+    }
+    */
+    // END Android-removed: Android doesn't load user-provided implementation.
+
     private void readObjectNoData (ObjectInputStream s) throws
                          IOException, ClassNotFoundException {
         // Android-changed: Don't use null to mean the boot classloader.
@@ -912,6 +1601,8 @@
 
     static final int NETID_UNSET = 0;
 
+    // BEGIN Android-added: Add methods required by frameworks/base.
+    // Particularly those required to deal with net-ids and scope ids.
     /**
      * Returns true if the string is a valid numeric IPv4 or IPv6 address (such as "192.168.0.1").
      * This copes with all forms of address that Java supports, detailed in the {@link InetAddress}
@@ -1007,6 +1698,7 @@
     public static InetAddress[] getAllByNameOnNet(String host, int netId) throws UnknownHostException {
         return impl.lookupAllHostAddr(host, netId).clone();
     }
+    // END Android-added: Add methods required by frameworks/base.
 
     // Only called by java.net.SocketPermission.
     static InetAddress[] getAllByName0(String authHost, boolean check) throws UnknownHostException {
@@ -1018,3 +1710,18 @@
         throw new UnsupportedOperationException();
     }
 }
+// BEGIN Android-removed: Android doesn't load user-provided implementation.
+/*
+ * Simple factory to create the impl
+ *
+class InetAddressImplFactory {
+
+    static InetAddressImpl create() {
+        return InetAddress.loadImpl(isIPv6Supported() ?
+                                    "Inet6AddressImpl" : "Inet4AddressImpl");
+    }
+
+    static native boolean isIPv6Supported();
+}
+*/
+// END Android-removed: Android doesn't load user-provided implementation.
diff --git a/java/net/InetAddressImpl.java b/java/net/InetAddressImpl.java
index e5821a3..a636c1e 100644
--- a/java/net/InetAddressImpl.java
+++ b/java/net/InetAddressImpl.java
@@ -35,6 +35,13 @@
  * @since 1.4
  */
 interface InetAddressImpl {
+
+    // BEGIN Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
+    /*
+    String getLocalHostName() throws UnknownHostException;
+    InetAddress[]
+        lookupAllHostAddr(String hostname) throws UnknownHostException;
+     */
     /**
      * Lookup all addresses for {@code hostname} on the given {@code netId}.
      */
@@ -49,12 +56,15 @@
      * Clear address caches (if any).
      */
     public void clearAddressCache();
+    // END Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
 
     /**
      * Return the "any" local address.
      */
     InetAddress anyLocalAddress();
 
+    // Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
+    // InetAddress loopbackAddress();
     /**
      * Return a list of loop back adresses for this implementation.
      */
diff --git a/java/net/InetSocketAddress.java b/java/net/InetSocketAddress.java
index 407c722..74b559b 100644
--- a/java/net/InetSocketAddress.java
+++ b/java/net/InetSocketAddress.java
@@ -151,6 +151,7 @@
         return hostname;
     }
 
+    // BEGIN Android-added: InetSocketAddress() ctor used by IoBridge.
     /**
      * @hide internal use only
      */
@@ -158,6 +159,7 @@
         // These will be filled in the native implementation of recvfrom.
         holder = new InetSocketAddressHolder(null, null, 0);
     }
+    // END Android-added: InetSocketAddress() ctor used by IoBridge.
 
     /**
      * Creates a socket address where the IP address is the wildcard address
@@ -172,7 +174,9 @@
      * range of valid port values.
      */
     public InetSocketAddress(int port) {
-      this((InetAddress)null, port);
+        // Android-changed: Defaults to IPv6.
+        // this(InetAddress.anyLocalAddress(), port);
+        this((InetAddress)null, port);
     }
 
     /**
@@ -193,7 +197,7 @@
     public InetSocketAddress(InetAddress addr, int port) {
         holder = new InetSocketAddressHolder(
                         null,
-                        // Android-changed: Return IPv4 address
+                        // Android-changed: Defaults to IPv6 if addr is null.
                         // addr == null ? InetAddress.anyLocalAddress() : addr,
                         addr == null ? Inet6Address.ANY : addr,
                         checkPort(port));
@@ -207,7 +211,7 @@
      * If that attempt fails, the address will be flagged as <I>unresolved</I>.
      * <p>
      * If there is a security manager, its {@code checkConnect} method
-     * is called with the host name as its argument to check the permissiom
+     * is called with the host name as its argument to check the permission
      * to resolve it. This could result in a SecurityException.
      * <P>
      * A valid port value is between 0 and 65535.
@@ -400,7 +404,7 @@
      * Two instances of {@code InetSocketAddress} represent the same
      * address if both the InetAddresses (or hostnames if it is unresolved) and port
      * numbers are equal.
-     * If both addresses are unresolved, then the hostname & the port number
+     * If both addresses are unresolved, then the hostname and the port number
      * are compared.
      *
      * Note: Hostnames are case insensitive. e.g. "FooBar" and "foobar" are
diff --git a/java/net/InterfaceAddress.java b/java/net/InterfaceAddress.java
index fbd0e48..85f9e58 100644
--- a/java/net/InterfaceAddress.java
+++ b/java/net/InterfaceAddress.java
@@ -46,6 +46,7 @@
     InterfaceAddress() {
     }
 
+    // BEGIN Android-added: Rewrote NetworkInterface on top of Libcore.io.
     InterfaceAddress(InetAddress address, Inet4Address broadcast, InetAddress netmask) {
         this.address = address;
         this.broadcast = broadcast;
@@ -65,6 +66,7 @@
         }
         return count;
     }
+    // END Android-added: Rewrote NetworkInterface on top of Libcore.io.
 
     /**
      * Returns an {@code InetAddress} for this address.
diff --git a/java/net/MulticastSocket.java b/java/net/MulticastSocket.java
index d4150a8..d14500a 100644
--- a/java/net/MulticastSocket.java
+++ b/java/net/MulticastSocket.java
@@ -567,6 +567,7 @@
      * @since 1.4
      */
     public NetworkInterface getNetworkInterface() throws SocketException {
+        // Android-changed: Support Integer IP_MULTICAST_IF2 values for app compat.
         Integer niIndex
             = (Integer)getImpl().getOption(SocketOptions.IP_MULTICAST_IF2);
         if (niIndex == 0) {
diff --git a/java/net/NetworkInterface.java b/java/net/NetworkInterface.java
index 216094a..e465d0b 100644
--- a/java/net/NetworkInterface.java
+++ b/java/net/NetworkInterface.java
@@ -45,6 +45,8 @@
 
 import static android.system.OsConstants.*;
 
+// Android-note: NetworkInterface has been rewritten to avoid native code.
+// Fix upstream bug not returning link-down interfaces. http://b/26238832
 /**
  * This class represents a Network Interface made up of a name,
  * and a list of IP addresses assigned to this interface.
@@ -61,14 +63,30 @@
     private int index;
     private InetAddress addrs[];
     private InterfaceAddress bindings[];
+    // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
+    // private NetworkInterface childs[];
     private List<NetworkInterface> childs;
     private NetworkInterface parent = null;
     private boolean virtual = false;
-    private byte[] hardwareAddr;
     private static final NetworkInterface defaultInterface;
     private static final int defaultIndex; /* index of defaultInterface */
 
+    // Android-changed: Fix upstream bug not returning link-down interfaces. http://b/26238832
+    private byte[] hardwareAddr;
+
     static {
+        // Android-removed: Android doesn't need to call native init.
+        /*
+        AccessController.doPrivileged(
+            new java.security.PrivilegedAction<Void>() {
+                public Void run() {
+                    System.loadLibrary("net");
+                    return null;
+                }
+            });
+
+        init();
+        */
         defaultInterface = DefaultInterface.getDefault();
         if (defaultInterface != null) {
             defaultIndex = defaultInterface.getIndex();
@@ -203,6 +221,7 @@
      * @since 1.6
      */
     public Enumeration<NetworkInterface> getSubInterfaces() {
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         return Collections.enumeration(childs);
     }
 
@@ -266,6 +285,7 @@
         if (name == null)
             throw new NullPointerException();
 
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         NetworkInterface[] nis = getAll();
         for (NetworkInterface ni : nis) {
             if (ni.getName().equals(name)) {
@@ -290,6 +310,7 @@
         if (index < 0)
             throw new IllegalArgumentException("Interface index can't be negative");
 
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         NetworkInterface[] nis = getAll();
         for (NetworkInterface ni : nis) {
             if (ni.getIndex() == index) {
@@ -329,6 +350,7 @@
             throw new IllegalArgumentException ("invalid address type");
         }
 
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         NetworkInterface[] nis = getAll();
         for (NetworkInterface ni : nis) {
             for (InetAddress inetAddress : Collections.list(ni.getInetAddresses())) {
@@ -357,6 +379,7 @@
         throws SocketException {
         final NetworkInterface[] netifs = getAll();
 
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         // specified to return null if no network interfaces
         if (netifs.length == 0)
             return null;
@@ -364,6 +387,9 @@
         return Collections.enumeration(Arrays.asList(netifs));
     }
 
+    // BEGIN Android-changed: Rewrote NetworkInterface on top of Libcore.io.
+    // private native static NetworkInterface[] getAll()
+    //    throws SocketException;
     private static NetworkInterface[] getAll() throws SocketException {
         // Group Ifaddrs by interface name.
         Map<String, List<StructIfaddrs>> inetMap = new HashMap<>();
@@ -439,6 +465,7 @@
 
         return nis.values().toArray(new NetworkInterface[nis.size()]);
     }
+    // END Android-changed: Rewrote NetworkInterface on top of Libcore.io.
 
     /**
      * Returns whether a network interface is up and running.
@@ -449,6 +476,7 @@
      */
 
     public boolean isUp() throws SocketException {
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         return (getFlags() & IFF_UP) != 0;
     }
 
@@ -461,6 +489,7 @@
      */
 
     public boolean isLoopback() throws SocketException {
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         return (getFlags() & IFF_LOOPBACK) != 0;
     }
 
@@ -476,6 +505,7 @@
      */
 
     public boolean isPointToPoint() throws SocketException {
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         return (getFlags() & IFF_POINTOPOINT) != 0;
     }
 
@@ -488,6 +518,7 @@
      */
 
     public boolean supportsMulticast() throws SocketException {
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
         return (getFlags() & IFF_MULTICAST) != 0;
     }
 
@@ -506,13 +537,21 @@
      * @since 1.6
      */
     public byte[] getHardwareAddress() throws SocketException {
-        // Android chage - do not use the cached address, fetch
-        // the object again. NI might not be valid anymore.
+        // BEGIN Android-changed: Fix upstream not returning link-down interfaces. http://b/26238832
+        /*
+        for (InetAddress addr : addrs) {
+            if (addr instanceof Inet4Address) {
+                return getMacAddr0(((Inet4Address)addr).getAddress(), name, index);
+            }
+        }
+        return getMacAddr0(null, name, index);
+         */
         NetworkInterface ni = getByName(name);
         if (ni == null) {
             throw new SocketException("NetworkInterface doesn't exist anymore");
         }
         return ni.hardwareAddr;
+        // END Android-changed: Fix upstream not returning link-down interfaces. http://b/26238832
     }
 
     /**
@@ -523,6 +562,8 @@
      * @since 1.6
      */
     public int getMTU() throws SocketException {
+        // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
+        // return getMTU0(name, index);
         FileDescriptor fd = null;
         try {
             fd = Libcore.rawOs.socket(AF_INET, SOCK_DGRAM, 0);
@@ -553,6 +594,18 @@
         return virtual;
     }
 
+    // BEGIN Android-removed: Rewrote NetworkInterface on top of Libcore.io.
+    /*
+    private native static boolean isUp0(String name, int ind) throws SocketException;
+    private native static boolean isLoopback0(String name, int ind) throws SocketException;
+    private native static boolean supportsMulticast0(String name, int ind) throws SocketException;
+    private native static boolean isP2P0(String name, int ind) throws SocketException;
+    private native static byte[] getMacAddr0(byte[] inAddr, String name, int ind) throws SocketException;
+    private native static int getMTU0(String name, int ind) throws SocketException;
+    */
+    // END Android-removed: Rewrote NetworkInterface on top of Libcore.io.
+
+    // BEGIN Android-added: Rewrote NetworkInterface on top of Libcore.io.
     private int getFlags() throws SocketException {
         FileDescriptor fd = null;
         try {
@@ -566,6 +619,7 @@
             IoUtils.closeQuietly(fd);
         }
     }
+    // END Android-added: Rewrote NetworkInterface on top of Libcore.io.
 
     /**
      * Compares this object against the specified object.
@@ -639,6 +693,9 @@
         return result;
     }
 
+    // Android-removed: Android doesn't need to call native init.
+    // private static native void init();
+
     /**
      * Returns the default network interface of this system
      *
diff --git a/java/net/PlainDatagramSocketImpl.java b/java/net/PlainDatagramSocketImpl.java
index c6a44d5..d4b1f2c 100644
--- a/java/net/PlainDatagramSocketImpl.java
+++ b/java/net/PlainDatagramSocketImpl.java
@@ -95,6 +95,7 @@
         }
     }
 
+    // BEGIN Android-changed: Rewrote on top of Libcore.io.
     protected synchronized void bind0(int lport, InetAddress laddr) throws SocketException {
         if (isClosed()) {
             throw new SocketException("Socket closed");
@@ -245,6 +246,7 @@
             IoBridge.connect(fd, inetAddressUnspec, 0);
         } catch (SocketException ignored) { }
     }
+    // END Android-changed: Rewrote on top of Libcore.io.
 
     // Android-removed: JNI has been removed
     // /**
diff --git a/java/net/PlainSocketImpl.java b/java/net/PlainSocketImpl.java
index 656defc..f592398 100644
--- a/java/net/PlainSocketImpl.java
+++ b/java/net/PlainSocketImpl.java
@@ -61,6 +61,13 @@
 
 class PlainSocketImpl extends AbstractPlainSocketImpl
 {
+    // Android-removed: Android doesn't need to call native initProto.
+    /*
+    static {
+        initProto();
+    }
+    */
+
     /**
      * Constructs an empty instance.
      */
@@ -101,6 +108,7 @@
         return (T)flow;
     }
 
+    // BEGIN Android-changed: Rewrote on top of Libcore.io.
     protected void socketSetOption(int opt, Object val) throws SocketException {
         try {
             socketSetOption0(opt, val);
@@ -293,5 +301,6 @@
             throw errnoException.rethrowAsSocketException();
         }
     }
+    // END Android-changed: Rewrote on top of Libcore.io.
 
 }
diff --git a/java/net/PortUnreachableException.java b/java/net/PortUnreachableException.java
index c5dae1b..3e7558b 100644
--- a/java/net/PortUnreachableException.java
+++ b/java/net/PortUnreachableException.java
@@ -51,6 +51,7 @@
      */
     public PortUnreachableException() {}
 
+    // Android-added: PortUnreachableException ctor used by IoBridge.
     /** @hide */
     public PortUnreachableException(String msg, Throwable cause) {
         super(msg, cause);
diff --git a/java/net/ProtocolException.java b/java/net/ProtocolException.java
index e5e66f7..0368350 100644
--- a/java/net/ProtocolException.java
+++ b/java/net/ProtocolException.java
@@ -55,6 +55,7 @@
     public ProtocolException() {
     }
 
+    // Android-added: ProtocolException ctor used by frameworks.
     /** @hide */
     public ProtocolException(String msg, Throwable cause) {
         super(msg, cause);
diff --git a/java/net/ServerSocket.java b/java/net/ServerSocket.java
index 20ae95a..bb495e6 100644
--- a/java/net/ServerSocket.java
+++ b/java/net/ServerSocket.java
@@ -253,6 +253,7 @@
      * @since 1.4
      * @hide
      */
+    // Android-changed: Make ctor public and @hide, for internal use.
     public SocketImpl getImpl() throws SocketException {
         if (!created)
             createImpl();
@@ -446,8 +447,7 @@
     }
 
     /**
-     * Returns the address of the endpoint this socket is bound to, or
-     * {@code null} if it is not bound yet.
+     * Returns the address of the endpoint this socket is bound to.
      * <p>
      * If the socket was bound prior to being {@link #close closed},
      * then this method will continue to return the address of the endpoint
@@ -922,7 +922,7 @@
         /* Not implemented yet */
     }
 
-    // Android-added: for testing and internal use.
+    // Android-added: getFileDescriptor$(), for testing / internal use.
     /**
      * @hide internal use only
      */
diff --git a/java/net/URLConnection.java b/java/net/URLConnection.java
index b869c39..1de7b3c 100644
--- a/java/net/URLConnection.java
+++ b/java/net/URLConnection.java
@@ -288,6 +288,13 @@
     */
     private static FileNameMap fileNameMap;
 
+    // BEGIN Android-removed: Android has its own mime table.
+    /*
+     * @since 1.2.2
+     *
+    private static boolean fileNameMapLoaded = false;
+    */
+    // END Android-removed: Android has its own mime table.
     /**
      * Loads filename map (a mimetable) from a data file. It will
      * first try to load the user-specific table, defined
@@ -299,6 +306,7 @@
      * @see #setFileNameMap(java.net.FileNameMap)
      */
     public static synchronized FileNameMap getFileNameMap() {
+        // Android-changed: Android has its own mime table.
         if (fileNameMap == null) {
             fileNameMap = new DefaultFileNameMap();
         }
@@ -352,6 +360,7 @@
      */
     abstract public void connect() throws IOException;
 
+    // Android-changed: Add javadoc to specify Android's timeout behavior.
     /**
      * Sets a specified timeout value, in milliseconds, to be used
      * when opening a communications link to the resource referenced
@@ -659,7 +668,7 @@
      * Returns the key for the {@code n}<sup>th</sup> header field.
      * It returns {@code null} if there are fewer than {@code n+1} fields.
      *
-     * @param   n   an index, where n>=0
+     * @param   n   an index, where {@code n>=0}
      * @return  the key for the {@code n}<sup>th</sup> header field,
      *          or {@code null} if there are fewer than {@code n+1}
      *          fields.
@@ -677,7 +686,7 @@
      * {@link #getHeaderFieldKey(int) getHeaderFieldKey} method to iterate through all
      * the headers in the message.
      *
-     * @param   n   an index, where n>=0
+     * @param   n   an index, where {@code n>=0}
      * @return  the value of the {@code n}<sup>th</sup> header field
      *          or {@code null} if there are fewer than {@code n+1} fields
      * @see     java.net.URLConnection#getHeaderFieldKey(int)
@@ -1234,6 +1243,7 @@
     {
         String contentType = stripOffParameters(getContentType());
         ContentHandler handler = null;
+        // BEGIN Android-changed: App Compat. Android guesses content type from name and stream.
         if (contentType == null) {
             if ((contentType = guessContentTypeFromName(url.getFile())) == null) {
                 contentType = guessContentTypeFromStream(getInputStream());
@@ -1243,6 +1253,7 @@
         if (contentType == null) {
             return UnknownContentHandler.INSTANCE;
         }
+        // END Android-changed: App Compat. Android guesses content type from name and stream.
         try {
             handler = handlers.get(contentType);
             if (handler != null)
diff --git a/java/net/URLDecoder.java b/java/net/URLDecoder.java
index cdf0419..8b14eb8 100644
--- a/java/net/URLDecoder.java
+++ b/java/net/URLDecoder.java
@@ -67,7 +67,7 @@
  * <p>
  * There are two possible ways in which this decoder could deal with
  * illegal strings.  It could either leave illegal characters alone or
- * it could throw an {@code {@link java.lang.IllegalArgumentException}}.
+ * it could throw an {@link java.lang.IllegalArgumentException}.
  * Which approach the decoder takes is left to the
  * implementation.
  *
@@ -172,14 +172,15 @@
 
                     while ( ((i+2) < numChars) &&
                             (c=='%')) {
-                        // BEGIN Android-changed
+                        // BEGIN Android-changed: App compat. Forbid non-hex chars after '%'.
                         if (!isValidHexChar(s.charAt(i+1)) || !isValidHexChar(s.charAt(i+2))) {
                             throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern : "
                                     + s.substring(i, i + 3));
                         }
-                        // END Android-changed
+                        // END Android-changed: App compat. Forbid non-hex chars after '%'.
                         int v = Integer.parseInt(s.substring(i+1,i+3),16);
                         if (v < 0)
+                            // Android-changed: Improve error message by printing the string value.
                             throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern - negative value : "
                                     + s.substring(i, i + 3));
                         bytes[pos++] = (byte) v;
@@ -213,9 +214,9 @@
         return (needToChange? sb.toString() : s);
     }
 
-    // BEGIN Android-changed
+    // BEGIN Android-added: App compat. Forbid non-hex chars after '%'.
     private static boolean isValidHexChar(char c) {
         return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
     }
-    // END Android-changed
+    // END Android-added: App compat. Forbid non-hex chars after '%'.
 }
diff --git a/java/security/SecureRandom.java b/java/security/SecureRandom.java
index 0852cbd..1e8707a 100644
--- a/java/security/SecureRandom.java
+++ b/java/security/SecureRandom.java
@@ -300,41 +300,6 @@
             instance.provider, algorithm);
     }
 
-    // BEGIN Android-added: Support for Crypto provider workaround
-    /**
-     * Maximum SDK version for which the workaround for the Crypto provider is in place.
-     *
-     * <p> We provide instances from the Crypto provider (although the provider is not installed) to
-     * apps targeting M or earlier versions of the SDK.
-     *
-     * <p> Default is 23 (M). We have it as a field for testability and it shouldn't be changed.
-     *
-     * @hide
-     */
-    public static final int DEFAULT_SDK_TARGET_FOR_CRYPTO_PROVIDER_WORKAROUND = 23;
-
-    private static int sdkTargetForCryptoProviderWorkaround =
-            DEFAULT_SDK_TARGET_FOR_CRYPTO_PROVIDER_WORKAROUND;
-
-    /**
-     * Only for testing.
-     *
-     * @hide
-     */
-    public static void setSdkTargetForCryptoProviderWorkaround(int sdkTargetVersion) {
-        sdkTargetForCryptoProviderWorkaround = sdkTargetVersion;
-    }
-
-    /**
-     * Only for testing.
-     *
-     * @hide
-     */
-    public static int getSdkTargetForCryptoProviderWorkaround() {
-        return sdkTargetForCryptoProviderWorkaround;
-    }
-    // END Android-added: Support for Crypto provider workaround
-
     /**
      * Returns a SecureRandom object that implements the specified
      * Random Number Generator (RNG) algorithm.
@@ -380,55 +345,12 @@
      */
     public static SecureRandom getInstance(String algorithm, String provider)
             throws NoSuchAlgorithmException, NoSuchProviderException {
-        try {
-            Instance instance = GetInstance.getInstance("SecureRandom",
-                    SecureRandomSpi.class, algorithm, provider);
-            return new SecureRandom((SecureRandomSpi) instance.impl,
-                    instance.provider, algorithm);
-        // BEGIN Android-added: Crypto provider deprecation
-        } catch (NoSuchProviderException nspe) {
-            if ("Crypto".equals(provider)) {
-                System.logE(" ********** PLEASE READ ************ ");
-                System.logE(" * ");
-                System.logE(" * New versions of the Android SDK no longer support the Crypto provider.");
-                System.logE(" * If your app was relying on setSeed() to derive keys from strings, you");
-                System.logE(" * should switch to using SecretKeySpec to load raw key bytes directly OR");
-                System.logE(" * use a real key derivation function (KDF). See advice here : ");
-                System.logE(" * http://android-developers.blogspot.com/2016/06/security-crypto-provider-deprecated-in.html ");
-                System.logE(" *********************************** ");
-                if (VMRuntime.getRuntime().getTargetSdkVersion()
-                        <= sdkTargetForCryptoProviderWorkaround) {
-                    System.logE(" Returning an instance of SecureRandom from the Crypto provider");
-                    System.logE(" as a temporary measure so that the apps targeting earlier SDKs");
-                    System.logE(" keep working. Please do not rely on the presence of the Crypto");
-                    System.logE(" provider in the codebase, as our plan is to delete it");
-                    System.logE(" completely in the future.");
-                    return getInstanceFromCryptoProvider(algorithm);
-                }
-            }
-
-            throw nspe;
-        }
+        Instance instance = GetInstance.getInstance("SecureRandom",
+            SecureRandomSpi.class, algorithm, provider);
+        return new SecureRandom((SecureRandomSpi)instance.impl,
+            instance.provider, algorithm);
     }
 
-    private static SecureRandom getInstanceFromCryptoProvider(String algorithm)
-            throws NoSuchAlgorithmException {
-        Provider cryptoProvider;
-        try {
-            cryptoProvider = (Provider) SecureRandom.class.getClassLoader()
-                    .loadClass(
-                            "org.apache.harmony.security.provider.crypto.CryptoProvider")
-                    .newInstance();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        Service service = cryptoProvider.getService("SecureRandom", algorithm);
-        Instance instance = GetInstance.getInstance(service, SecureRandomSpi.class);
-        return new SecureRandom(
-                (SecureRandomSpi) instance.impl, instance.provider, algorithm);
-    }
-    // END Android-added: Crypto provider deprecation
-
     /**
      * Returns a SecureRandom object that implements the specified
      * Random Number Generator (RNG) algorithm.
@@ -679,7 +601,7 @@
     /**
      * Returns a {@code SecureRandom} object.
      *
-     * In Android this is equivalent to get a SHA1PRNG from OpenSSLProvider.
+     * In Android this is equivalent to get a SHA1PRNG from AndroidOpenSSL.
      *
      * Some situations require strong random values, such as when
      * creating high-value/long-lived secrets like RSA public/private
diff --git a/java/text/Bidi.java b/java/text/Bidi.java
index 21085f2..70e29aa 100644
--- a/java/text/Bidi.java
+++ b/java/text/Bidi.java
@@ -81,6 +81,13 @@
      */
     public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1;
 
+    // Android-note: Upstream this class delegates to an internal implementation class BidiBase.
+    // For Android that is replaced with android.icu.text.Bidi. BidiBase and ICU Bidi work very
+    // similarly, but differ in some details like level of argument validation and how how exactly
+    // runs are counted. The majority of the changes in this file exist to allow for backwards
+    // compatibility with an earlier ICU4C based Bidi implementation.
+
+    // BEGIN Android-added: translateConstToIcu(int).
     private static int translateConstToIcu(int javaInt) {
         switch (javaInt) {
             case DIRECTION_DEFAULT_LEFT_TO_RIGHT:
@@ -96,8 +103,10 @@
                 return android.icu.text.Bidi.DIRECTION_LEFT_TO_RIGHT;
         }
     }
+    // END Android-added: translateConstToIcu(int).
 
-    private android.icu.text.Bidi bidiBase;
+    // Android-changed: use ICU Bidi class instead of BidiBase.
+    private final android.icu.text.Bidi bidiBase;
 
     /**
      * Create Bidi from the given paragraph of text and base direction.
@@ -108,8 +117,13 @@
      * Other values are reserved.
      */
     public Bidi(String paragraph, int flags) {
-        this((paragraph == null ? null : paragraph.toCharArray()), 0, null, 0,
-                (paragraph == null ? 0 : paragraph.length()), flags);
+        if (paragraph == null) {
+            throw new IllegalArgumentException("paragraph is null");
+        }
+
+        // Android-changed: use ICU Bidi class instead of BidiBase.
+        bidiBase = new android.icu.text.Bidi(paragraph.toCharArray(), 0, null, 0,
+                                             paragraph.length(), translateConstToIcu(flags));
     }
 
     /**
@@ -142,6 +156,7 @@
             throw new IllegalArgumentException("paragraph is null");
         }
 
+        // Android-changed: change from BidiBase to ICU Bidi class.
         this.bidiBase = new android.icu.text.Bidi(paragraph);
     }
 
@@ -180,10 +195,12 @@
                                                " for embeddings of length: " + text.length);
         }
 
+        // Android-changed: use ICU Bidi class instead of BidiBase.
         bidiBase = new android.icu.text.Bidi(text, textStart, embeddings, embStart,
                                              paragraphLength, translateConstToIcu(flags));
     }
 
+    // Android-added: private constructor based on ICU Bidi object.
     private Bidi(android.icu.text.Bidi bidiBase) {
         this.bidiBase = bidiBase;
     }
@@ -198,6 +215,7 @@
      * @return a {@code Bidi} object
      */
     public Bidi createLineBidi(int lineStart, int lineLimit) {
+        // BEGIN Android-changed: add explict argument checks and use ICU Bidi class.
         if (lineStart < 0 || lineLimit < 0 || lineStart > lineLimit || lineLimit > getLength()) {
             throw new IllegalArgumentException("Invalid ranges (start=" + lineStart + ", " +
                                                "limit=" + lineLimit + ", length=" + getLength() + ")");
@@ -214,6 +232,7 @@
          }
 
         return new Bidi(bidiBase.createLineBidi(lineStart, lineLimit));
+        // END Android-changed: add explict argument checks and use ICU Bidi class.
     }
 
     /**
@@ -276,11 +295,13 @@
      * @return the resolved level of the character at offset
      */
     public int getLevelAt(int offset) {
+        // BEGIN Android-changed: return base level on out of range offset argument.
         try {
             return bidiBase.getLevelAt(offset);
         } catch (IllegalArgumentException e) {
             return getBaseLevel();
         }
+        // END Android-changed: return base level on out of range offset argument.
     }
 
     /**
@@ -288,6 +309,7 @@
      * @return the number of level runs
      */
     public int getRunCount() {
+        // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
         int runCount = bidiBase.countRuns();
         return (runCount == 0 ? 1 : runCount);
     }
@@ -298,11 +320,11 @@
      * @return the level of the run
      */
     public int getRunLevel(int run) {
-        // Paper over a the ICU4J behaviour of strictly enforcing run must be strictly less than
-        // the number of runs. Done to maintain compatibility with previous C implementation.
+        // Android-added: Tolerate calls with run == getRunCount() for backwards compatibility.
         if (run == getRunCount()) {
             return getBaseLevel();
         }
+        // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
         return (bidiBase.countRuns() == 0 ? bidiBase.getBaseLevel() : bidiBase.getRunLevel(run));
     }
 
@@ -313,11 +335,11 @@
      * @return the start of the run
      */
     public int getRunStart(int run) {
-        // Paper over a the ICU4J behaviour of strictly enforcing run must be strictly less than
-        // the number of runs. Done to maintain compatibility with previous C implementation.
+        // Android-added: Tolerate calls with run == getRunCount() for backwards compatibility.
         if (run == getRunCount()) {
             return getBaseLevel();
         }
+        // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
         return (bidiBase.countRuns() == 0 ? 0 : bidiBase.getRunStart(run));
     }
 
@@ -329,11 +351,11 @@
      * @return limit the limit of the run
      */
     public int getRunLimit(int run) {
-        // Paper over a the ICU4J behaviour of strictly enforcing run must be strictly less than
-        // the number of runs. Done to maintain compatibility with previous C implementation.
+        // Android-added: Tolerate calls with run == getRunCount() for backwards compatibility.
         if (run == getRunCount()) {
             return getBaseLevel();
         }
+        // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
         return (bidiBase.countRuns() == 0 ? bidiBase.getLength() : bidiBase.getRunLimit(run));
     }
 
@@ -349,6 +371,7 @@
      * @return true if the range of characters requires bidi analysis
      */
     public static boolean requiresBidi(char[] text, int start, int limit) {
+        // Android-added: Check arguments to throw correct exception.
         if (0 > start || start > limit || limit > text.length) {
             throw new IllegalArgumentException("Value start " + start +
                                                " is out of range 0 to " + limit);
@@ -373,6 +396,7 @@
      * @param count the number of objects to reorder
      */
     public static void reorderVisually(byte[] levels, int levelStart, Object[] objects, int objectStart, int count) {
+        // BEGIN Android-added: Check arguments to throw correct exception.
         if (0 > levelStart || levels.length <= levelStart) {
             throw new IllegalArgumentException("Value levelStart " +
                       levelStart + " is out of range 0 to " +
@@ -388,6 +412,9 @@
                       levelStart + " is out of range 0 to " +
                       (objects.length - objectStart));
         }
+        // END Android-added: Check arguments to throw correct exception.
+
+        // Android-changed: use ICU Bidi class instead of BidiBase.
         android.icu.text.Bidi.reorderVisually(levels, levelStart, objects, objectStart, count);
     }
 
@@ -395,8 +422,10 @@
      * Display the bidi internal state, used in debugging.
      */
     public String toString() {
+        // Android-changed: construct String representation from ICU Bidi object values.
         return getClass().getName()
             + "[direction: " + bidiBase.getDirection() + " baseLevel: " + bidiBase.getBaseLevel()
             + " length: " + bidiBase.getLength() + " runs: " + bidiBase.getRunCount() + "]";
     }
+
 }
diff --git a/java/text/BreakIterator.java b/java/text/BreakIterator.java
index 6e7e053..e9adafa 100644
--- a/java/text/BreakIterator.java
+++ b/java/text/BreakIterator.java
@@ -424,6 +424,8 @@
      */
     public abstract void setText(CharacterIterator newText);
 
+    // Android-removed: Removed code related to BreakIteratorProvider support.
+
     /**
      * Returns a new <code>BreakIterator</code> instance
      * for <a href="BreakIterator.html#word">word breaks</a>
@@ -528,7 +530,9 @@
                 android.icu.text.BreakIterator.getSentenceInstance(locale));
     }
 
-    // Android-changed: Removed references to BreakIteratorProvider.
+    // Android-removed: Removed code related to BreakIteratorProvider support.
+
+    // Android-changed: Removed references to BreakIteratorProvider from JavaDoc.
     /**
      * Returns an array of all locales for which the
      * <code>get*Instance</code> methods of this class can return
diff --git a/java/text/ChoiceFormat.java b/java/text/ChoiceFormat.java
index 1ac8627..3baaa90 100644
--- a/java/text/ChoiceFormat.java
+++ b/java/text/ChoiceFormat.java
@@ -208,6 +208,7 @@
                     } else if (tempBuffer.equals("-\u221E")) {
                         startValue = Double.NEGATIVE_INFINITY;
                     } else {
+                        // Android-changed: avoid object instantiation followed by unboxing.
                         startValue = Double.parseDouble(segments[0].toString());
                     }
                 } catch (Exception e) {
@@ -349,6 +350,7 @@
         choiceFormats = Arrays.copyOf(formats, formats.length);
     }
 
+    // Android-changed: Clarify that calling setChoices() changes what is returned here.
     /**
      * @return a copy of the {@code double[]} array supplied to the constructor or the most recent
      * call to {@link #setChoices(double[], String[])}.
@@ -358,6 +360,7 @@
         return newLimits;
     }
 
+    // Android-changed: Clarify that calling setChoices() changes what is returned here.
     /**
      * @return a copy of the {@code String[]} array supplied to the constructor or the most recent
      * call to {@link #setChoices(double[], String[])}.
diff --git a/java/text/CollationElementIterator.java b/java/text/CollationElementIterator.java
index ad31f4a..fede747 100644
--- a/java/text/CollationElementIterator.java
+++ b/java/text/CollationElementIterator.java
@@ -107,10 +107,15 @@
      * Null order which indicates the end of string is reached by the
      * cursor.
      */
+    // Android-changed: use ICU CollationElementIterator constant.
     public final static int NULLORDER = android.icu.text.CollationElementIterator.NULLORDER;
 
+    // Android-removed: internal constructors.
+
+    // Android-added: ICU iterator to delegate to.
     private android.icu.text.CollationElementIterator icuIterator;
 
+   // Android-added: internal constructor taking an ICU CollationElementIterator.
     CollationElementIterator(android.icu.text.CollationElementIterator iterator) {
         icuIterator = iterator;
     }
@@ -121,6 +126,7 @@
      */
     public void reset()
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         icuIterator.reset();
     }
 
@@ -142,6 +148,7 @@
      */
     public int next()
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         return icuIterator.next();
     }
 
@@ -164,6 +171,7 @@
      */
     public int previous()
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         return icuIterator.previous();
     }
 
@@ -174,6 +182,7 @@
      */
     public final static int primaryOrder(int order)
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         return android.icu.text.CollationElementIterator.primaryOrder(order);
     }
     /**
@@ -183,6 +192,7 @@
      */
     public final static short secondaryOrder(int order)
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
        return (short) android.icu.text.CollationElementIterator.secondaryOrder(order);
     }
     /**
@@ -192,6 +202,7 @@
      */
     public final static short tertiaryOrder(int order)
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         return (short) android.icu.text.CollationElementIterator.tertiaryOrder(order);
     }
 
@@ -213,6 +224,7 @@
     @SuppressWarnings("deprecation") // getBeginIndex, getEndIndex and setIndex are deprecated
     public void setOffset(int newOffset)
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         icuIterator.setOffset(newOffset);
     }
 
@@ -232,9 +244,11 @@
      */
     public int getOffset()
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         return icuIterator.getOffset();
     }
 
+
     /**
      * Return the maximum length of any expansion sequences that end
      * with the specified comparison order.
@@ -245,6 +259,7 @@
      */
     public int getMaxExpansion(int order)
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         return icuIterator.getMaxExpansion(order);
     }
 
@@ -256,6 +271,7 @@
      */
     public void setText(String source)
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         icuIterator.setText(source);
     }
 
@@ -267,6 +283,9 @@
      */
     public void setText(CharacterIterator source)
     {
+        // Android-changed: delegate to ICU CollationElementIterator.
         icuIterator.setText(source);
     }
+
+    // Android-removed: private helper methods and fields.
 }
diff --git a/java/text/Collator.java b/java/text/Collator.java
index 01e7dc9..ca3a220 100644
--- a/java/text/Collator.java
+++ b/java/text/Collator.java
@@ -226,10 +226,9 @@
      * @see java.util.Locale
      * @see java.util.ResourceBundle
      */
-    public static synchronized
-    Collator getInstance(Locale desiredLocale)
+    // Android-changed: Switched to ICU.
+    public static synchronized Collator getInstance(Locale desiredLocale)
     {
-        // Android-changed: Switched to ICU.
         if (desiredLocale == null) {
             throw new NullPointerException("locale == null");
         }
@@ -302,6 +301,7 @@
      */
     public boolean equals(String source, String target)
     {
+        // Android-changed: remove use of unnecessary EQUAL constant.
         return (compare(source, target) == 0);
     }
 
@@ -400,6 +400,7 @@
         return ICU.getAvailableCollatorLocales();
     }
 
+    // BEGIN Android-added: conversion method for decompositionMode constants.
     private int decompositionMode_Java_ICU(int mode) {
         switch (mode) {
             case Collator.CANONICAL_DECOMPOSITION:
@@ -422,7 +423,9 @@
         }
         return javaMode;
     }
+    // END Android-added: conversion method for decompositionMode constants.
 
+    // Android-changed: improve documentation.
     /**
      * Returns a new collator with the same decomposition mode and
      * strength value as this collator.
@@ -484,9 +487,13 @@
         icuColl = android.icu.text.RuleBasedCollator.getInstance(Locale.getDefault());
     }
 
+    // Android-added: ICU Collator this delegates to.
     android.icu.text.Collator icuColl;
 
+    // Android-added: protected constructor taking a Collator.
     Collator(android.icu.text.Collator icuColl) {
         this.icuColl = icuColl;
     }
+
+    // Android-removed: Fields and constants.
 }
diff --git a/java/text/DateFormatSymbols.java b/java/text/DateFormatSymbols.java
index 96a966c..305f6f2 100644
--- a/java/text/DateFormatSymbols.java
+++ b/java/text/DateFormatSymbols.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2016, 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
@@ -47,7 +47,6 @@
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.Objects;
-import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
@@ -142,6 +141,8 @@
         initializeData(locale);
     }
 
+    // Android-removed: unused private DateFormatSymbols(boolean) constructor.
+
     /**
      * Era strings. For example: "AD" and "BC".  An array of 2 strings,
      * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
@@ -227,10 +228,8 @@
     /**
      * Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
      * All locales use the same these unlocalized pattern characters.
-     *
-     * Pretend to support 'L' and 'c' for now. It's meant for standalone weekday and
-     * month names, but we just use the non-standalone versions for now.
      */
+    // Android-changed: Add 'c' (standalone day of week).
     static final String  patternChars = "GyMdkHmsSEDFwWahKzZYuXLc";
 
     static final int PATTERN_ERA                  =  0; // G
@@ -256,6 +255,7 @@
     static final int PATTERN_ISO_DAY_OF_WEEK      = 20; // u
     static final int PATTERN_ISO_ZONE             = 21; // X
     static final int PATTERN_MONTH_STANDALONE     = 22; // L
+    // Android-added: Constant for standalone day of week.
     static final int PATTERN_STANDALONE_DAY_OF_WEEK = 23; // c
 
     /**
@@ -281,6 +281,7 @@
     /* use serialVersionUID from JDK 1.1.4 for interoperability */
     static final long serialVersionUID = -5987973545549424702L;
 
+    // BEGIN Android-added: Android specific serialization code.
     // the internal serial version which says which version was written
     // - 0 (default) for version up to JDK 1.1.4
     // - 1 Android version that contains a whole bunch of new fields.
@@ -299,7 +300,9 @@
      * @since JDK1.1.4
      */
     private int serialVersionOnStream = currentSerialVersion;
+    // END Android-added: Android specific serialization code.
 
+    // BEGIN Android-added: Support for tiny and standalone field names.
     /**
      * Tiny month strings; "J", "F", "M" etc.
      *
@@ -355,6 +358,7 @@
      * @serial
      */
     private String[] tinyStandAloneWeekdays;
+    // END Android-added: Support for tiny and standalone field names.
 
     // Android-changed: Removed reference to DateFormatSymbolsProvider.
     /**
@@ -411,6 +415,8 @@
         return getCachedInstance(locale);
     }
 
+    // BEGIN Android-changed: Replace getProviderInstance() with getCachedInstance().
+    // Android removed support for DateFormatSymbolsProviders, but still caches DFS.
     /**
      * Returns a cached DateFormatSymbols if it's found in the
      * cache. Otherwise, this method returns a newly cached instance
@@ -418,10 +424,10 @@
      */
     private static DateFormatSymbols getCachedInstance(Locale locale) {
         SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
-        DateFormatSymbols dfs = null;
+        DateFormatSymbols dfs;
         if (ref == null || (dfs = ref.get()) == null) {
             dfs = new DateFormatSymbols(locale);
-            ref = new SoftReference<DateFormatSymbols>(dfs);
+            ref = new SoftReference<>(dfs);
             SoftReference<DateFormatSymbols> x = cachedInstances.putIfAbsent(locale, ref);
             if (x != null) {
                 DateFormatSymbols y = x.get();
@@ -435,6 +441,7 @@
         }
         return dfs;
     }
+    // END Android-changed: Replace getProviderInstance() with getCachedInstance().
 
     /**
      * Gets era strings. For example: "AD" and "BC".
@@ -455,6 +462,17 @@
 
     /**
      * Gets month strings. For example: "January", "February", etc.
+     *
+     * <p>If the language requires different forms for formatting and
+     * stand-alone usages, this method returns month names in the
+     * formatting form. For example, the preferred month name for
+     * January in the Czech language is <em>ledna</em> in the
+     * formatting form, while it is <em>leden</em> in the stand-alone
+     * form. This method returns {@code "ledna"} in this case. Refer
+     * to the <a href="http://unicode.org/reports/tr35/#Calendar_Elements">
+     * Calendar Elements in the Unicode Locale Data Markup Language
+     * (LDML) specification</a> for more details.
+     *
      * @return the month strings.
      */
     public String[] getMonths() {
@@ -472,6 +490,17 @@
 
     /**
      * Gets short month strings. For example: "Jan", "Feb", etc.
+     *
+     * <p>If the language requires different forms for formatting and
+     * stand-alone usages, This method returns short month names in
+     * the formatting form. For example, the preferred abbreviation
+     * for January in the Catalan language is <em>de gen.</em> in the
+     * formatting form, while it is <em>gen.</em> in the stand-alone
+     * form. This method returns {@code "de gen."} in this case. Refer
+     * to the <a href="http://unicode.org/reports/tr35/#Calendar_Elements">
+     * Calendar Elements in the Unicode Locale Data Markup Language
+     * (LDML) specification</a> for more details.
+     *
      * @return the short month strings.
      */
     public String[] getShortMonths() {
@@ -648,6 +677,7 @@
         cachedHashCode = 0;
     }
 
+    // BEGIN Android-added: Support for tiny and standalone field names.
     String[] getTinyMonths() {
         return tinyMonths;
     }
@@ -679,6 +709,7 @@
     String[] getTinyStandAloneWeekdays() {
         return tinyStandAloneWeekdays;
     }
+    // END Android-added: Support for tiny and standalone field names.
 
     /**
      * Overrides Cloneable
@@ -727,6 +758,7 @@
         if (this == obj) return true;
         if (obj == null || getClass() != obj.getClass()) return false;
         DateFormatSymbols that = (DateFormatSymbols) obj;
+        // BEGIN Android-changed: Avoid populating zoneStrings just for the comparison, add fields.
         if (!(Arrays.equals(eras, that.eras)
                 && Arrays.equals(months, that.months)
                 && Arrays.equals(shortMonths, that.shortMonths)
@@ -747,11 +779,11 @@
                   && that.localPatternChars == null)))) {
             return false;
         }
-        // Android-changed: Avoid populating zoneStrings just for the comparison.
         if (!isZoneStringsSet && !that.isZoneStringsSet && Objects.equals(locale, that.locale)) {
             return true;
         }
         return Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper());
+        // END Android-changed: Avoid populating zoneStrings just for the comparison.
     }
 
     // =======================privates===============================
@@ -765,7 +797,7 @@
      * Cache to hold DateFormatSymbols instances per Locale.
      */
     private static final ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> cachedInstances
-        = new ConcurrentHashMap<Locale, SoftReference<DateFormatSymbols>>(3);
+        = new ConcurrentHashMap<>(3);
 
     private transient int lastZoneIndex = 0;
 
@@ -774,35 +806,43 @@
      */
     transient volatile int cachedHashCode = 0;
 
-    private void initializeData(Locale desiredLocale) {
-        locale = desiredLocale;
-
-        // Copy values of a cached instance if any.
+    // Android-changed: update comment to describe local modification.
+    /**
+     * Initializes this DateFormatSymbols with the locale data. This method uses
+     * a cached DateFormatSymbols instance for the given locale if available. If
+     * there's no cached one, this method populates this objects fields from an
+     * appropriate LocaleData object. Note: zoneStrings isn't initialized in this method.
+     */
+    private void initializeData(Locale locale) {
         SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
         DateFormatSymbols dfs;
+        // Android-changed: invert cache presence check to simplify code flow.
         if (ref != null && (dfs = ref.get()) != null) {
             copyMembers(dfs, this);
             return;
         }
+
+        // BEGIN Android-changed: Use ICU data and move cache handling to getCachedInstance().
         locale = LocaleData.mapInvalidAndNullLocales(locale);
         LocaleData localeData = LocaleData.get(locale);
 
+        this.locale = locale;
         eras = localeData.eras;
-
-        // Month names.
         months = localeData.longMonthNames;
         shortMonths = localeData.shortMonthNames;
-
         ampms = localeData.amPm;
         localPatternChars = patternChars;
 
-        // Weekdays.
         weekdays = localeData.longWeekdayNames;
         shortWeekdays = localeData.shortWeekdayNames;
 
         initializeSupplementaryData(localeData);
+        // END Android-changed: Use ICU data and move cache handling to getCachedInstance().
     }
 
+    // Android-removed: toOneBasedArray(String[])
+
+    // BEGIN Android-added: initializeSupplementaryData(LocaleData) for tiny and standalone fields.
     private void initializeSupplementaryData(LocaleData localeData) {
         // Tiny weekdays and months.
         tinyMonths = localeData.tinyMonthNames;
@@ -818,6 +858,7 @@
         shortStandAloneWeekdays = localeData.shortStandAloneWeekdayNames;
         tinyStandAloneWeekdays = localeData.tinyStandAloneWeekdayNames;
     }
+    // END Android-added: initializeSupplementaryData(LocaleData) for tiny and standalone fields.
 
     /**
      * Package private: used by SimpleDateFormat
@@ -866,14 +907,14 @@
     }
 
     // BEGIN Android-changed: extract initialization of zoneStrings to separate method.
-    private final synchronized String[][] internalZoneStrings() {
+    private synchronized String[][] internalZoneStrings() {
         if (zoneStrings == null) {
             zoneStrings = TimeZoneNames.getZoneStrings(locale);
         }
         return zoneStrings;
     }
 
-    private final String[][] getZoneStringsImpl(boolean needsCopy) {
+    private String[][] getZoneStringsImpl(boolean needsCopy) {
         String[][] zoneStrings = internalZoneStrings();
         // END Android-changed: extract initialization of zoneStrings to separate method.
 
@@ -895,12 +936,14 @@
 
     /**
      * Clones all the data members from the source DateFormatSymbols to
-     * the target DateFormatSymbols. This is only for subclasses.
+     * the target DateFormatSymbols.
+     *
      * @param src the source DateFormatSymbols.
      * @param dst the target DateFormatSymbols.
      */
     private void copyMembers(DateFormatSymbols src, DateFormatSymbols dst)
     {
+        dst.locale = src.locale;
         dst.eras = Arrays.copyOf(src.eras, src.eras.length);
         dst.months = Arrays.copyOf(src.months, src.months.length);
         dst.shortMonths = Arrays.copyOf(src.shortMonths, src.shortMonths.length);
@@ -915,6 +958,7 @@
         dst.localPatternChars = src.localPatternChars;
         dst.cachedHashCode = 0;
 
+        // BEGIN Android-added: Support for tiny and standalone field names.
         dst.tinyMonths = src.tinyMonths;
         dst.tinyWeekdays = src.tinyWeekdays;
 
@@ -925,8 +969,10 @@
         dst.standAloneWeekdays = src.standAloneWeekdays;
         dst.shortStandAloneWeekdays = src.shortStandAloneWeekdays;
         dst.tinyStandAloneWeekdays = src.tinyStandAloneWeekdays;
+        // END Android-added: Support for tiny and standalone field names.
     }
 
+    // BEGIN Android-added: support reading non-Android serialized DFS.
     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
         stream.defaultReadObject();
 
@@ -937,6 +983,7 @@
 
         serialVersionOnStream = currentSerialVersion;
     }
+    // END Android-added: support reading non-Android serialized DFS.
 
     /**
      * Write out the default serializable data, after ensuring the
diff --git a/java/text/DecimalFormat.java b/java/text/DecimalFormat.java
index 17d5d41..d2e8530 100644
--- a/java/text/DecimalFormat.java
+++ b/java/text/DecimalFormat.java
@@ -381,6 +381,12 @@
  */
 public class DecimalFormat extends NumberFormat {
 
+    // Android-note: This class is heavily modified from upstream OpenJDK.
+    // Android's version delegates most of its work to android.icu.text.DecimalFormat. This is done
+    // to avoid code duplication and to stay compatible with earlier releases that used ICU4C/ICU4J
+    // to implement DecimalFormat.
+
+    // Android-added: ICU DecimalFormat to delegate to.
     private transient android.icu.text.DecimalFormat icuDecimalFormat;
 
     /**
@@ -402,6 +408,7 @@
     public DecimalFormat() {
         // Get the pattern for the default locale.
         Locale def = Locale.getDefault(Locale.Category.FORMAT);
+        // BEGIN Android-changed: Use ICU LocaleData.
         // try to get the pattern from the cache
         String pattern = cachedLocaleData.get(def);
         if (pattern == null) {  /* cache miss */
@@ -410,8 +417,11 @@
             /* update cache */
             cachedLocaleData.putIfAbsent(def, pattern);
         }
-        this.symbols = new DecimalFormatSymbols(def);
-        init(pattern);
+        // END Android-changed: Use ICU LocaleData.
+        // Always applyPattern after the symbols are set
+        this.symbols = DecimalFormatSymbols.getInstance(def);
+        // Android-changed: use initPattern() instead of removed applyPattern(String, boolean).
+        initPattern(pattern);
     }
 
 
@@ -435,8 +445,10 @@
      * @see java.text.NumberFormat#getPercentInstance
      */
     public DecimalFormat(String pattern) {
-        this.symbols = new DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT));
-        init(pattern);
+        // Always applyPattern after the symbols are set
+        this.symbols = DecimalFormatSymbols.getInstance(Locale.getDefault(Locale.Category.FORMAT));
+        // Android-changed: use initPattern() instead of removed applyPattern(String, boolean).
+        initPattern(pattern);
     }
 
 
@@ -464,45 +476,69 @@
     public DecimalFormat (String pattern, DecimalFormatSymbols symbols) {
         // Always applyPattern after the symbols are set
         this.symbols = (DecimalFormatSymbols)symbols.clone();
-        init(pattern);
+        // Android-changed: use initPattern() instead of removed applyPattern(String, boolean).
+        initPattern(pattern);
     }
 
-    private void init(String pattern) {
+    // BEGIN Android-added: initPattern() and conversion methods between ICU and Java values.
+    /**
+     * Applies the pattern similarly to {@link #applyPattern(String)}, except it initializes
+     * {@link #icuDecimalFormat} in the process. This should only be called from constructors.
+     */
+    private void initPattern(String pattern) {
         this.icuDecimalFormat =  new android.icu.text.DecimalFormat(pattern,
                 symbols.getIcuDecimalFormatSymbols());
         updateFieldsFromIcu();
     }
 
     /**
+     * Update local fields indicating maximum/minimum integer/fraction digit count from the ICU
+     * DecimalFormat. This needs to be called whenever a new pattern is applied.
+     */
+    private void updateFieldsFromIcu() {
+        // Imitate behaviour of ICU4C NumberFormat that Android used up to M.
+        // If the pattern doesn't enforce a different value (some exponential
+        // patterns do), then set the maximum integer digits to 2 billion.
+        if (icuDecimalFormat.getMaximumIntegerDigits() == DOUBLE_INTEGER_DIGITS) {
+            icuDecimalFormat.setMaximumIntegerDigits(2000000000);
+        }
+        maximumIntegerDigits = icuDecimalFormat.getMaximumIntegerDigits();
+        minimumIntegerDigits = icuDecimalFormat.getMinimumIntegerDigits();
+        maximumFractionDigits = icuDecimalFormat.getMaximumFractionDigits();
+        minimumFractionDigits = icuDecimalFormat.getMinimumFractionDigits();
+    }
+
+    /**
      * Converts between field positions used by Java/ICU.
      * @param fp The java.text.NumberFormat.Field field position
      * @return The android.icu.text.NumberFormat.Field field position
      */
     private static FieldPosition getIcuFieldPosition(FieldPosition fp) {
-        if (fp.getFieldAttribute() == null) return fp;
+        Format.Field fieldAttribute = fp.getFieldAttribute();
+        if (fieldAttribute == null) return fp;
 
         android.icu.text.NumberFormat.Field attribute;
-        if (fp.getFieldAttribute() == Field.INTEGER) {
+        if (fieldAttribute == Field.INTEGER) {
             attribute = android.icu.text.NumberFormat.Field.INTEGER;
-        } else if (fp.getFieldAttribute() == Field.FRACTION) {
+        } else if (fieldAttribute == Field.FRACTION) {
             attribute = android.icu.text.NumberFormat.Field.FRACTION;
-        } else if (fp.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
+        } else if (fieldAttribute == Field.DECIMAL_SEPARATOR) {
             attribute = android.icu.text.NumberFormat.Field.DECIMAL_SEPARATOR;
-        } else if (fp.getFieldAttribute() == Field.EXPONENT_SYMBOL) {
+        } else if (fieldAttribute == Field.EXPONENT_SYMBOL) {
             attribute = android.icu.text.NumberFormat.Field.EXPONENT_SYMBOL;
-        } else if (fp.getFieldAttribute() == Field.EXPONENT_SIGN) {
+        } else if (fieldAttribute == Field.EXPONENT_SIGN) {
             attribute = android.icu.text.NumberFormat.Field.EXPONENT_SIGN;
-        } else if (fp.getFieldAttribute() == Field.EXPONENT) {
+        } else if (fieldAttribute == Field.EXPONENT) {
             attribute = android.icu.text.NumberFormat.Field.EXPONENT;
-        } else if (fp.getFieldAttribute() == Field.GROUPING_SEPARATOR) {
+        } else if (fieldAttribute == Field.GROUPING_SEPARATOR) {
             attribute = android.icu.text.NumberFormat.Field.GROUPING_SEPARATOR;
-        } else if (fp.getFieldAttribute() == Field.CURRENCY) {
+        } else if (fieldAttribute == Field.CURRENCY) {
             attribute = android.icu.text.NumberFormat.Field.CURRENCY;
-        } else if (fp.getFieldAttribute() == Field.PERCENT) {
+        } else if (fieldAttribute == Field.PERCENT) {
             attribute = android.icu.text.NumberFormat.Field.PERCENT;
-        } else if (fp.getFieldAttribute() == Field.PERMILLE) {
+        } else if (fieldAttribute == Field.PERMILLE) {
             attribute = android.icu.text.NumberFormat.Field.PERMILLE;
-        } else if (fp.getFieldAttribute() == Field.SIGN) {
+        } else if (fieldAttribute == Field.SIGN) {
             attribute = android.icu.text.NumberFormat.Field.SIGN;
         } else {
             throw new IllegalArgumentException("Unexpected field position attribute type.");
@@ -521,41 +557,43 @@
      * @return Field converted to a java.text.NumberFormat.Field field.
      */
     private static Field toJavaFieldAttribute(AttributedCharacterIterator.Attribute icuAttribute) {
-        if (icuAttribute.getName().equals(Field.INTEGER.getName())) {
+        String name = icuAttribute.getName();
+        if (name.equals(Field.INTEGER.getName())) {
             return Field.INTEGER;
         }
-        if (icuAttribute.getName().equals(Field.CURRENCY.getName())) {
+        if (name.equals(Field.CURRENCY.getName())) {
             return Field.CURRENCY;
         }
-        if (icuAttribute.getName().equals(Field.DECIMAL_SEPARATOR.getName())) {
+        if (name.equals(Field.DECIMAL_SEPARATOR.getName())) {
             return Field.DECIMAL_SEPARATOR;
         }
-        if (icuAttribute.getName().equals(Field.EXPONENT.getName())) {
+        if (name.equals(Field.EXPONENT.getName())) {
             return Field.EXPONENT;
         }
-        if (icuAttribute.getName().equals(Field.EXPONENT_SIGN.getName())) {
+        if (name.equals(Field.EXPONENT_SIGN.getName())) {
             return Field.EXPONENT_SIGN;
         }
-        if (icuAttribute.getName().equals(Field.EXPONENT_SYMBOL.getName())) {
+        if (name.equals(Field.EXPONENT_SYMBOL.getName())) {
             return Field.EXPONENT_SYMBOL;
         }
-        if (icuAttribute.getName().equals(Field.FRACTION.getName())) {
+        if (name.equals(Field.FRACTION.getName())) {
             return Field.FRACTION;
         }
-        if (icuAttribute.getName().equals(Field.GROUPING_SEPARATOR.getName())) {
+        if (name.equals(Field.GROUPING_SEPARATOR.getName())) {
             return Field.GROUPING_SEPARATOR;
         }
-        if (icuAttribute.getName().equals(Field.SIGN.getName())) {
+        if (name.equals(Field.SIGN.getName())) {
             return Field.SIGN;
         }
-        if (icuAttribute.getName().equals(Field.PERCENT.getName())) {
+        if (name.equals(Field.PERCENT.getName())) {
             return Field.PERCENT;
         }
-        if (icuAttribute.getName().equals(Field.PERMILLE.getName())) {
+        if (name.equals(Field.PERMILLE.getName())) {
             return Field.PERMILLE;
         }
-        throw new IllegalArgumentException("Unrecognized attribute: " + icuAttribute.getName());
-   }
+        throw new IllegalArgumentException("Unrecognized attribute: " + name);
+    }
+    // END Android-added: initPattern() and conversion methods between ICU and Java values.
 
     // Overrides
     /**
@@ -614,13 +652,17 @@
     @Override
     public StringBuffer format(double number, StringBuffer result,
                                FieldPosition fieldPosition) {
+        // BEGIN Android-changed: Use ICU.
         FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
         icuDecimalFormat.format(number, result, icuFieldPosition);
         fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
         fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
         return result;
+        // END Android-changed: Use ICU.
     }
 
+    // Android-removed: private StringBuffer format(double, StringBuffer, FieldDelegate).
+
     /**
      * Format a long to produce a string.
      * @param number    The long to format
@@ -635,13 +677,17 @@
     @Override
     public StringBuffer format(long number, StringBuffer result,
                                FieldPosition fieldPosition) {
+        // BEGIN Android-changed: Use ICU.
         FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
         icuDecimalFormat.format(number, result, icuFieldPosition);
         fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
         fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
         return result;
+        // END Android-changed: Use ICU.
     }
 
+    // Android-removed: private StringBuffer format(long, StringBuffer, FieldDelegate).
+
     /**
      * Formats a BigDecimal to produce a string.
      * @param number    The BigDecimal to format
@@ -655,13 +701,17 @@
      */
     private StringBuffer format(BigDecimal number, StringBuffer result,
                                 FieldPosition fieldPosition) {
+        // BEGIN Android-changed: Use ICU.
         FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
         icuDecimalFormat.format(number, result, fieldPosition);
         fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
         fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
         return result;
+        // END Android-changed: Use ICU.
     }
 
+    // Android-removed: private StringBuffer format(BigDecimal, StringBuffer, FieldDelegate).
+
     /**
      * Format a BigInteger to produce a string.
      * @param number    The BigInteger to format
@@ -675,13 +725,17 @@
      */
     private StringBuffer format(BigInteger number, StringBuffer result,
                                FieldPosition fieldPosition) {
+        // BEGIN Android-changed: Use ICU.
         FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
         icuDecimalFormat.format(number, result, fieldPosition);
         fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
         fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
         return result;
+        // END Android-changed: Use ICU.
     }
 
+    // Android-removed: private StringBuffer format(BigInteger, StringBuffer, FieldDelegate).
+
     /**
      * Formats an Object producing an <code>AttributedCharacterIterator</code>.
      * You can use the returned <code>AttributedCharacterIterator</code>
@@ -703,6 +757,7 @@
      */
     @Override
     public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+        // BEGIN Android-changed: Use ICU.
         if (obj == null) {
             throw new NullPointerException("object == null");
         }
@@ -736,8 +791,13 @@
         }
 
         return result.getIterator();
+        // END Android-changed: Use ICU.
     }
 
+    // Android-removed: "fast-path formating logic for double" (sic).
+
+    // Android-removed: subformat(), append().
+
     /**
      * Parses text from a string to produce a <code>Number</code>.
      * <p>
@@ -796,6 +856,7 @@
      */
     @Override
     public Number parse(String text, ParsePosition pos) {
+        // BEGIN Android-changed: Use ICU.
         // Return early if the parse position is bogus.
         if (pos.index < 0 || pos.index >= text.length()) {
             return null;
@@ -829,8 +890,11 @@
             return 0L;
         }
         return number;
+        // END Android-changed: Use ICU.
     }
 
+    // Android-removed: STATUS_* constants, multiplier fields and methods and subparse(String, ...).
+
     /**
      * Returns a copy of the decimal format symbols, which is generally not
      * changed by the programmer or user.
@@ -838,6 +902,7 @@
      * @see java.text.DecimalFormatSymbols
      */
     public DecimalFormatSymbols getDecimalFormatSymbols() {
+        // Android-changed: Use ICU.
         return DecimalFormatSymbols.fromIcuInstance(icuDecimalFormat.getDecimalFormatSymbols());
     }
 
@@ -852,6 +917,7 @@
         try {
             // don't allow multiple references
             symbols = (DecimalFormatSymbols) newSymbols.clone();
+            // Android-changed: Use ICU.
             icuDecimalFormat.setDecimalFormatSymbols(symbols.getIcuDecimalFormatSymbols());
         } catch (Exception foo) {
             // should never happen
@@ -865,6 +931,7 @@
      * @return the positive prefix
      */
     public String getPositivePrefix () {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.getPositivePrefix();
     }
 
@@ -875,9 +942,12 @@
      * @param newValue the new positive prefix
      */
     public void setPositivePrefix (String newValue) {
+        // Android-changed: Use ICU.
         icuDecimalFormat.setPositivePrefix(newValue);
     }
 
+    // Android-removed: private helper getPositivePrefixFieldPositions().
+
     /**
      * Get the  prefix.
      * <P>Examples: -123, ($123) (with negative suffix), sFr-123
@@ -885,6 +955,7 @@
      * @return the negative prefix
      */
     public String getNegativePrefix () {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.getNegativePrefix();
     }
 
@@ -895,9 +966,12 @@
      * @param newValue the new negative prefix
      */
     public void setNegativePrefix (String newValue) {
+        // Android-changed: Use ICU.
         icuDecimalFormat.setNegativePrefix(newValue);
     }
 
+    // Android-removed: private helper getNegativePrefixFieldPositions().
+
     /**
      * Get the positive suffix.
      * <P>Example: 123%
@@ -905,6 +979,7 @@
      * @return the positive suffix
      */
     public String getPositiveSuffix () {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.getPositiveSuffix();
     }
 
@@ -915,9 +990,12 @@
      * @param newValue the new positive suffix
      */
     public void setPositiveSuffix (String newValue) {
+        // Android-changed: Use ICU.
         icuDecimalFormat.setPositiveSuffix(newValue);
     }
 
+    // Android-removed: private helper getPositiveSuffixFieldPositions().
+
     /**
      * Get the negative suffix.
      * <P>Examples: -123%, ($123) (with positive suffixes)
@@ -925,6 +1003,7 @@
      * @return the negative suffix
      */
     public String getNegativeSuffix () {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.getNegativeSuffix();
     }
 
@@ -935,9 +1014,12 @@
      * @param newValue the new negative suffix
      */
     public void setNegativeSuffix (String newValue) {
+        // Android-changed: Use ICU.
         icuDecimalFormat.setNegativeSuffix(newValue);
     }
 
+    // Android-removed: private helper getNegativeSuffixFieldPositions().
+
     /**
      * Gets the multiplier for use in percent, per mille, and similar
      * formats.
@@ -946,6 +1028,7 @@
      * @see #setMultiplier(int)
      */
     public int getMultiplier () {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.getMultiplier();
     }
 
@@ -968,6 +1051,27 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setGroupingUsed(boolean newValue) {
+        // Android-changed: Use ICU.
+        icuDecimalFormat.setGroupingUsed(newValue);
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
+    }
+
+    // BEGIN Android-added: isGroupingUsed() override delegating to ICU.
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isGroupingUsed() {
+        return icuDecimalFormat.isGroupingUsed();
+    }
+    // END Android-added: isGroupingUsed() override delegating to ICU.
+
+    /**
      * Return the grouping size. Grouping size is the number of digits between
      * grouping separators in the integer portion of a number.  For example,
      * in the number "123,456.78", the grouping size is 3.
@@ -978,6 +1082,7 @@
      * @see java.text.DecimalFormatSymbols#getGroupingSeparator
      */
     public int getGroupingSize () {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.getGroupingSize();
     }
 
@@ -994,26 +1099,10 @@
      * @see java.text.DecimalFormatSymbols#setGroupingSeparator
      */
     public void setGroupingSize (int newValue) {
+        // Android-changed: Use ICU.
         icuDecimalFormat.setGroupingSize(newValue);
-    }
-
-    /**
-     * Returns true if grouping is used in this format. For example, in the
-     * English locale, with grouping on, the number 1234567 might be formatted
-     * as "1,234,567". The grouping separator as well as the size of each group
-     * is locale dependant and is determined by sub-classes of NumberFormat.
-     * @see #setGroupingUsed
-     */
-    public boolean isGroupingUsed() {
-        return icuDecimalFormat.isGroupingUsed();
-    }
-
-    /**
-     * Set whether or not grouping will be used in this format.
-     * @see #isGroupingUsed
-     */
-    public void setGroupingUsed(boolean newValue) {
-        icuDecimalFormat.setGroupingUsed(newValue);
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
     }
 
     /**
@@ -1025,6 +1114,7 @@
      *         {@code false} otherwise
      */
     public boolean isDecimalSeparatorAlwaysShown() {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.isDecimalSeparatorAlwaysShown();
     }
 
@@ -1037,6 +1127,7 @@
      *                 {@code false} otherwise
      */
     public void setDecimalSeparatorAlwaysShown(boolean newValue) {
+        // Android-changed: Use ICU.
         icuDecimalFormat.setDecimalSeparatorAlwaysShown(newValue);
     }
 
@@ -1050,6 +1141,7 @@
      * @since 1.5
      */
     public boolean isParseBigDecimal() {
+        // Android-changed: Use ICU.
         return icuDecimalFormat.isParseBigDecimal();
     }
 
@@ -1063,35 +1155,35 @@
      * @since 1.5
      */
     public void setParseBigDecimal(boolean newValue) {
+        // Android-changed: Use ICU.
         icuDecimalFormat.setParseBigDecimal(newValue);
     }
 
+    // BEGIN Android-added: setParseIntegerOnly()/isParseIntegerOnly() overrides delegating to ICU.
     /**
-     * Sets whether or not numbers should be parsed as integers only.
-     * @see #isParseIntegerOnly
+     * {@inheritDoc}
      */
+    @Override
+    public boolean isParseIntegerOnly() {
+        return icuDecimalFormat.isParseIntegerOnly();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void setParseIntegerOnly(boolean value) {
         super.setParseIntegerOnly(value);
         icuDecimalFormat.setParseIntegerOnly(value);
     }
-
-    /**
-     * Returns true if this format will parse numbers as integers only.
-     * For example in the English locale, with ParseIntegerOnly true, the
-     * string "1234." would be parsed as the integer value 1234 and parsing
-     * would stop at the "." character.  Of course, the exact format accepted
-     * by the parse operation is locale dependant and determined by sub-classes
-     * of NumberFormat.
-     */
-    public boolean isParseIntegerOnly() {
-        return icuDecimalFormat.isParseIntegerOnly();
-    }
+    // END Android-added: setParseIntegerOnly()/isParseIntegerOnly() overrides delegating to ICU.
 
     /**
      * Standard override; no change in semantics.
      */
     @Override
     public Object clone() {
+        // BEGIN Android-changed: Use ICU, remove fast path related code.
         try {
             DecimalFormat other = (DecimalFormat) super.clone();
             other.icuDecimalFormat = (android.icu.text.DecimalFormat) icuDecimalFormat.clone();
@@ -1100,8 +1192,10 @@
         } catch (Exception e) {
             throw new InternalError();
         }
+        // END Android-changed: Use ICU, remove fast path related code.
     }
 
+    // BEGIN Android-changed: re-implement equals() using ICU fields.
     /**
      * Overrides equals
      */
@@ -1130,12 +1224,14 @@
         }
         return other.getRoundingIncrement() == null;
     }
+    // END Android-changed: re-implement equals() using ICU fields.
 
     /**
      * Overrides hashCode
      */
     @Override
     public int hashCode() {
+        // Android-changed: use getPositivePrefix() instead of positivePrefix field.
         return super.hashCode() * 37 + getPositivePrefix().hashCode();
         // just enough fields for a reasonable distribution
     }
@@ -1148,6 +1244,7 @@
      * @see #applyPattern
      */
     public String toPattern() {
+        // Android-changed: use ICU.
         return icuDecimalFormat.toPattern();
     }
 
@@ -1159,9 +1256,12 @@
      * @see #applyPattern
      */
     public String toLocalizedPattern() {
+        // Android-changed: use ICU.
         return icuDecimalFormat.toLocalizedPattern();
     }
 
+    // Android-removed: private helper methods expandAffixes(), expandAffix(), toPattern(boolean).
+
     /**
      * Apply the given pattern to this Format object.  A pattern is a
      * short-hand specification for the various formatting properties.
@@ -1185,11 +1285,11 @@
      * @exception IllegalArgumentException if the given pattern is invalid.
      */
     public void applyPattern(String pattern) {
+        // Android-changed: use ICU.
         icuDecimalFormat.applyPattern(pattern);
         updateFieldsFromIcu();
     }
 
-
     /**
      * Apply the given pattern to this Format object.  The pattern
      * is assumed to be in a localized notation. A pattern is a
@@ -1214,22 +1314,12 @@
      * @exception IllegalArgumentException if the given pattern is invalid.
      */
     public void applyLocalizedPattern(String pattern) {
+        // Android-changed: use ICU.
         icuDecimalFormat.applyLocalizedPattern(pattern);
         updateFieldsFromIcu();
     }
 
-    private void updateFieldsFromIcu() {
-        // Imitate behaviour of ICU4C NumberFormat that Android used up to M.
-        // If the pattern doesn't enforce a different value (some exponential
-        // patterns do), then set the maximum integer digits to 2 billion.
-        if (icuDecimalFormat.getMaximumIntegerDigits() == DOUBLE_INTEGER_DIGITS) {
-            icuDecimalFormat.setMaximumIntegerDigits(2000000000);
-        }
-        maximumIntegerDigits = icuDecimalFormat.getMaximumIntegerDigits();
-        minimumIntegerDigits = icuDecimalFormat.getMinimumIntegerDigits();
-        maximumFractionDigits = icuDecimalFormat.getMaximumFractionDigits();
-        minimumFractionDigits = icuDecimalFormat.getMinimumFractionDigits();
-    }
+    // Android-removed: applyPattern(String, boolean) as apply[Localized]Pattern calls ICU directly.
 
     /**
      * Sets the maximum number of digits allowed in the integer portion of a
@@ -1249,7 +1339,10 @@
             super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ?
                 DOUBLE_INTEGER_DIGITS : minimumIntegerDigits);
         }
+        // Android-changed: use ICU.
         icuDecimalFormat.setMaximumIntegerDigits(getMaximumIntegerDigits());
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
     }
 
     /**
@@ -1270,7 +1363,10 @@
             super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ?
                 DOUBLE_INTEGER_DIGITS : maximumIntegerDigits);
         }
+        // Android-changed: use ICU.
         icuDecimalFormat.setMinimumIntegerDigits(getMinimumIntegerDigits());
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
     }
 
     /**
@@ -1291,7 +1387,10 @@
             super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ?
                 DOUBLE_FRACTION_DIGITS : minimumFractionDigits);
         }
+        // Android-changed: use ICU.
         icuDecimalFormat.setMaximumFractionDigits(getMaximumFractionDigits());
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
     }
 
     /**
@@ -1312,7 +1411,10 @@
             super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ?
                 DOUBLE_FRACTION_DIGITS : maximumFractionDigits);
         }
+        // Android-changed: use ICU.
         icuDecimalFormat.setMinimumFractionDigits(getMinimumFractionDigits());
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
     }
 
     /**
@@ -1396,6 +1498,7 @@
      */
     @Override
     public void setCurrency(Currency currency) {
+        // BEGIN Android-changed: use ICU.
         // Set the international currency symbol, and currency symbol on the DecimalFormatSymbols
         // object and tell ICU to use that.
         if (currency != symbols.getCurrency()
@@ -1407,6 +1510,9 @@
             icuDecimalFormat.setMinimumFractionDigits(minimumFractionDigits);
             icuDecimalFormat.setMaximumFractionDigits(maximumFractionDigits);
         }
+        // END Android-changed: use ICU.
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
     }
 
     /**
@@ -1421,6 +1527,7 @@
         return roundingMode;
     }
 
+    // Android-added: convertRoundingMode() to convert between Java and ICU RoundingMode enums.
     private static int convertRoundingMode(RoundingMode rm) {
         switch (rm) {
         case UP:
@@ -1458,10 +1565,16 @@
         }
 
         this.roundingMode = roundingMode;
-
+        // Android-changed: use ICU.
         icuDecimalFormat.setRoundingMode(convertRoundingMode(roundingMode));
+        // Android-removed: fast path related code.
+        // fastPathCheckNeeded = true;
     }
 
+    // BEGIN Android-added: 7u40 version of adjustForCurrencyDefaultFractionDigits().
+    // This method was removed in OpenJDK 8 in favor of doing equivalent work in the provider. Since
+    // Android removed support for providers for NumberFormat we keep this method around as an
+    // "Android addition".
     /**
      * Adjusts the minimum and maximum fraction digits to values that
      * are reasonable for the currency's default fraction digits.
@@ -1490,9 +1603,9 @@
             }
         }
     }
+    // END Android-added: Upstream code from earlier OpenJDK release.
 
-    private static final int currentSerialVersion = 4;
-
+    // BEGIN Android-added: Custom serialization code for compatibility with RI serialization.
     // the fields list to be serialized
     private static final ObjectStreamField[] serialPersistentFields = {
             new ObjectStreamField("positivePrefix", String.class),
@@ -1545,6 +1658,7 @@
         fields.put("serialVersionOnStream", currentSerialVersion);
         stream.writeFields();
     }
+    // BEGIN Android-added: Custom serialization code for compatibility with RI serialization.
 
     /**
      * Reads the default serializable fields from the stream and performs
@@ -1589,12 +1703,13 @@
      * literal values.  This is exactly what we want, since that corresponds to
      * the pre-version-2 behavior.
      */
+    // BEGIN Android-added: Custom serialization code for compatibility with RI serialization.
     private void readObject(ObjectInputStream stream)
             throws IOException, ClassNotFoundException {
         ObjectInputStream.GetField fields = stream.readFields();
         this.symbols = (DecimalFormatSymbols) fields.get("symbols", null);
 
-        init("#");
+        initPattern("#");
 
         // Calling a setter method on an ICU DecimalFormat object will change the object's internal
         // state, even if the value set is the same as the default value (ICU Ticket #13266).
@@ -1683,11 +1798,14 @@
             setMinimumFractionDigits(super.getMinimumFractionDigits());
         }
     }
+    // END Android-added: Custom serialization code for compatibility with RI serialization.
 
     //----------------------------------------------------------------------
     // INSTANCE VARIABLES
     //----------------------------------------------------------------------
 
+    // Android-removed: various fields now stored in icuDecimalFormat.
+
     /**
      * The <code>DecimalFormatSymbols</code> object used by this format.
      * It contains the symbols used to format numbers, e.g. the grouping separator,
@@ -1697,7 +1815,9 @@
      * @see #setDecimalFormatSymbols
      * @see java.text.DecimalFormatSymbols
      */
-    private DecimalFormatSymbols symbols;
+    private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols();
+
+    // Android-removed: useExponentialNotation, *FieldPositions, minExponentDigits.
 
     /**
      * The maximum number of digits allowed in the integer portion of a
@@ -1709,7 +1829,8 @@
      * @see #getMaximumIntegerDigits
      * @since 1.5
      */
-    private int    maximumIntegerDigits;
+    // Android-changed: removed initialisation.
+    private int    maximumIntegerDigits /* = super.getMaximumIntegerDigits() */;
 
     /**
      * The minimum number of digits allowed in the integer portion of a
@@ -1721,7 +1842,8 @@
      * @see #getMinimumIntegerDigits
      * @since 1.5
      */
-    private int    minimumIntegerDigits;
+    // Android-changed: removed initialisation.
+    private int    minimumIntegerDigits /* = super.getMinimumIntegerDigits() */;
 
     /**
      * The maximum number of digits allowed in the fractional portion of a
@@ -1733,7 +1855,8 @@
      * @see #getMaximumFractionDigits
      * @since 1.5
      */
-    private int    maximumFractionDigits;
+    // Android-changed: removed initialisation.
+    private int    maximumFractionDigits /* = super.getMaximumFractionDigits() */;
 
     /**
      * The minimum number of digits allowed in the fractional portion of a
@@ -1745,7 +1868,8 @@
      * @see #getMinimumFractionDigits
      * @since 1.5
      */
-    private int    minimumFractionDigits;
+    // Android-changed: removed initialisation.
+    private int    minimumFractionDigits /* = super.getMinimumFractionDigits() */;
 
     /**
      * The {@link java.math.RoundingMode} used in this DecimalFormat.
@@ -1755,12 +1879,20 @@
      */
     private RoundingMode roundingMode = RoundingMode.HALF_EVEN;
 
+    // Android-removed: FastPathData, isFastPath, fastPathCheckNeeded and fastPathData.
 
+    //----------------------------------------------------------------------
+
+    static final int currentSerialVersion = 4;
+
+    // Android-removed: serialVersionOnStream.
 
     //----------------------------------------------------------------------
     // CONSTANTS
     //----------------------------------------------------------------------
 
+    // Android-removed: Fast-Path for double Constants, various constants.
+
     // Upper limit on integer and fraction digits for a Java double
     static final int DOUBLE_INTEGER_DIGITS  = 309;
     static final int DOUBLE_FRACTION_DIGITS = 340;
@@ -1772,9 +1904,10 @@
     // Proclaim JDK 1.1 serial compatibility.
     static final long serialVersionUID = 864413376551465018L;
 
+    // Android-added: cachedLocaleData for caching default number format pattern per locale.
     /**
      * Cache to hold the NumberPattern of a Locale.
      */
     private static final ConcurrentMap<Locale, String> cachedLocaleData
-        = new ConcurrentHashMap<Locale, String>(3);
+        = new ConcurrentHashMap<>(3);
 }
diff --git a/java/text/DecimalFormatSymbols.java b/java/text/DecimalFormatSymbols.java
index 2acb128..a9f11c8 100644
--- a/java/text/DecimalFormatSymbols.java
+++ b/java/text/DecimalFormatSymbols.java
@@ -66,8 +66,7 @@
 
 public class DecimalFormatSymbols implements Cloneable, Serializable {
 
-    // Android-changed: Removed reference to DecimalFormatSymbolsProvider but suggested
-    // getInstance() be used instead in case Android supports it in future.
+    // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
     /**
      * Create a DecimalFormatSymbols object for the default
      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
@@ -83,8 +82,7 @@
         initialize( Locale.getDefault(Locale.Category.FORMAT) );
     }
 
-    // Android-changed: Removed reference to DecimalFormatSymbolsProvider but suggested
-    // getInstance() be used instead in case Android supports it in future.
+    // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
     /**
      * Create a DecimalFormatSymbols object for the given locale.
      * It is recommended that the {@link #getInstance(Locale) getInstance} method is used
@@ -175,6 +173,7 @@
      */
     public void setZeroDigit(char zeroDigit) {
         this.zeroDigit = zeroDigit;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -194,6 +193,7 @@
      */
     public void setGroupingSeparator(char groupingSeparator) {
         this.groupingSeparator = groupingSeparator;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -213,6 +213,7 @@
      */
     public void setDecimalSeparator(char decimalSeparator) {
         this.decimalSeparator = decimalSeparator;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -232,6 +233,7 @@
      */
     public void setPerMill(char perMill) {
         this.perMill = perMill;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -244,6 +246,7 @@
         return percent;
     }
 
+    // Android-added: getPercentString() for percent signs longer than one char.
     /**
      * Gets the string used for percent sign. Different for Arabic, etc.
      *
@@ -260,6 +263,7 @@
      */
     public void setPercent(char percent) {
         this.percent = percent;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -279,6 +283,7 @@
      */
     public void setDigit(char digit) {
         this.digit = digit;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -300,6 +305,7 @@
      */
     public void setPatternSeparator(char patternSeparator) {
         this.patternSeparator = patternSeparator;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -321,6 +327,7 @@
      */
     public void setInfinity(String infinity) {
         this.infinity = infinity;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -342,6 +349,7 @@
      */
     public void setNaN(String NaN) {
         this.NaN = NaN;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -357,6 +365,7 @@
     }
 
 
+    // Android-added: getPercentString() for percent signs longer than one char.
     /**
      * Gets the string used to represent minus sign. If no explicit
      * negative format is specified, one is formed by prefixing
@@ -377,6 +386,7 @@
      */
     public void setMinusSign(char minusSign) {
         this.minusSign = minusSign;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -402,6 +412,7 @@
     public void setCurrencySymbol(String currency)
     {
         currencySymbol = currency;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -440,10 +451,12 @@
         if (currencyCode != null) {
             try {
                 currency = Currency.getInstance(currencyCode);
+                // Android-changed: get currencySymbol for locale.
                 currencySymbol = currency.getSymbol(locale);
             } catch (IllegalArgumentException e) {
             }
         }
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -478,6 +491,7 @@
         this.currency = currency;
         intlCurrencySymbol = currency.getCurrencyCode();
         currencySymbol = currency.getSymbol(locale);
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -502,6 +516,7 @@
     public void setMonetaryDecimalSeparator(char sep)
     {
         monetarySeparator = sep;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -535,6 +550,7 @@
     void setExponentialSymbol(char exp)
     {
         exponential = exp;
+        // Android-added: reset cachedIcuDFS.
         cachedIcuDFS = null;
     }
 
@@ -608,6 +624,7 @@
             int result = zeroDigit;
             result = result * 37 + groupingSeparator;
             result = result * 37 + decimalSeparator;
+            // BEGIN Android-added: more fields in hashcode calculation.
             result = result * 37 + percent;
             result = result * 37 + perMill;
             result = result * 37 + digit;
@@ -621,6 +638,7 @@
             result = result * 37 + monetarySeparator;
             result = result * 37 + exponentialSeparator.hashCode();
             result = result * 37 + locale.hashCode();
+           // END Android-added: more fields in hashcode calculation.
             return result;
     }
 
@@ -666,7 +684,7 @@
         minusSign = maybeStripMarkers(numberElements[6], '-');
         exponential = numberElements[7].charAt(0);
         exponentialSeparator = numberElements[7]; //string representation new since 1.6
-        perMill = numberElements[8].charAt(0);
+        perMill = maybeStripMarkers(numberElements[8], '\u2030');
         infinity  = numberElements[9];
         NaN = numberElements[10];
 
@@ -674,7 +692,7 @@
         // Check for empty country string separately because it's a valid
         // country ID for Locale (and used for the C locale), but not a valid
         // ISO 3166 country code, and exceptions are expensive.
-        if (!"".equals(locale.getCountry())) {
+        if (locale.getCountry().length() > 0) {
             try {
                 currency = Currency.getInstance(locale);
             } catch (IllegalArgumentException e) {
@@ -689,6 +707,7 @@
                 currencySymbol = currency.getSymbol(locale);
                 data[1] = intlCurrencySymbol;
                 data[2] = currencySymbol;
+                // Android-added: update cache when necessary.
                 needCacheUpdate = true;
             }
         } else {
@@ -705,6 +724,7 @@
         // If that changes, add a new entry to NumberElements.
         monetarySeparator = decimalSeparator;
 
+        // Android-added: update cache when necessary.
         if (needCacheUpdate) {
             cachedLocaleData.putIfAbsent(locale, data);
         }
@@ -743,6 +763,7 @@
         return fallback;
     }
 
+    // BEGIN Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
     /**
      * Convert an instance of this class to the ICU version so that it can be used with ICU4J.
      * @hide
@@ -753,6 +774,9 @@
         }
 
         cachedIcuDFS = new android.icu.text.DecimalFormatSymbols(this.locale);
+        // Do not localize plus sign. See "Special Pattern Characters" section in DecimalFormat.
+        // http://b/67034519
+        cachedIcuDFS.setPlusSign('+');
         cachedIcuDFS.setZeroDigit(zeroDigit);
         cachedIcuDFS.setDigit(digit);
         cachedIcuDFS.setDecimalSeparator(decimalSeparator);
@@ -763,6 +787,7 @@
         cachedIcuDFS.setMonetaryGroupingSeparator(groupingSeparator);
         cachedIcuDFS.setPatternSeparator(patternSeparator);
         cachedIcuDFS.setPercent(percent);
+        cachedIcuDFS.setPerMill(perMill);
         cachedIcuDFS.setMonetaryDecimalSeparator(monetarySeparator);
         cachedIcuDFS.setMinusSign(minusSign);
         cachedIcuDFS.setInfinity(infinity);
@@ -816,8 +841,9 @@
         result.setCurrencySymbol(dfs.getCurrencySymbol());
         return result;
     }
+    // END Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
 
-
+    // BEGIN Android-added: Android specific serialization code.
     private static final ObjectStreamField[] serialPersistentFields = {
             new ObjectStreamField("currencySymbol", String.class),
             new ObjectStreamField("decimalSeparator", char.class),
@@ -867,6 +893,7 @@
         fields.put("percentStr", getPercentString());
         stream.writeFields();
     }
+    // END Android-added: Android specific serialization code.
 
     /**
      * Reads the default serializable fields, provides default values for objects
@@ -885,7 +912,9 @@
      *
      * @since JDK 1.1.6
      */
-    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+    private void readObject(ObjectInputStream stream)
+            throws IOException, ClassNotFoundException {
+        // BEGIN Android-changed: Android specific serialization code.
         ObjectInputStream.GetField fields = stream.readFields();
         final int serialVersionOnStream = fields.get("serialVersionOnStream", 0);
         currencySymbol = (String) fields.get("currencySymbol", "");
@@ -939,6 +968,7 @@
         } catch (IllegalArgumentException e) {
             currency = null;
         }
+        // END Android-changed: Android specific serialization code.
     }
 
     /**
@@ -1109,11 +1139,18 @@
      */
     private int serialVersionOnStream = currentSerialVersion;
 
+    // BEGIN Android-added: cache for locale data and cachedIcuDFS.
     /**
      * cache to hold the NumberElements and the Currency
      * of a Locale.
      */
-    private static final ConcurrentHashMap<Locale, Object[]> cachedLocaleData = new ConcurrentHashMap<Locale, Object[]>(3);
+    private static final ConcurrentHashMap<Locale, Object[]> cachedLocaleData = new ConcurrentHashMap<>(3);
 
+    /**
+     * Lazily created cached instance of an ICU DecimalFormatSymbols that's equivalent to this one.
+     * This field is reset to null whenever any of the relevant fields of this class are modified
+     * and will be re-created by {@link #getIcuDecimalFormatSymbols()} as necessary.
+     */
     private transient android.icu.text.DecimalFormatSymbols cachedIcuDFS = null;
+    // END Android-added: cache for locale data and cachedIcuDFS.
 }
diff --git a/java/text/MessageFormat.java b/java/text/MessageFormat.java
index d7431ef..b900824 100644
--- a/java/text/MessageFormat.java
+++ b/java/text/MessageFormat.java
@@ -690,6 +690,7 @@
      *            larger than the number of format elements in the pattern string
      */
     public void setFormat(int formatElementIndex, Format newFormat) {
+        // Android-added: prevent setting unused formatters.
         if (formatElementIndex > maxOffset) {
             throw new ArrayIndexOutOfBoundsException(maxOffset, formatElementIndex);
         }
diff --git a/java/text/Normalizer.java b/java/text/Normalizer.java
index 4c551c3..8c22547 100644
--- a/java/text/Normalizer.java
+++ b/java/text/Normalizer.java
@@ -106,8 +106,7 @@
  */
 public final class Normalizer {
 
-    private Normalizer() {
-    }
+   private Normalizer() {};
 
     /**
      * This enum provides constants of the four Unicode normalization forms
@@ -118,6 +117,7 @@
      *
      * @since 1.6
      */
+    // BEGIN Android-changed: remove static modifier and add mapping to equivalent ICU values.
     public enum Form {
 
         /**
@@ -146,41 +146,42 @@
             this.icuMode = icuMode;
         }
     }
+    // END Android-changed: remove static modifier and add mapping to equivalent ICU values.
 
     /**
      * Normalize a sequence of char values.
      * The sequence will be normalized according to the specified normalization
      * from.
-     *
-     * @param src  The sequence of char values to normalize.
-     * @param form The normalization form; one of
-     *             {@link java.text.Normalizer.Form#NFC},
-     *             {@link java.text.Normalizer.Form#NFD},
-     *             {@link java.text.Normalizer.Form#NFKC},
-     *             {@link java.text.Normalizer.Form#NFKD}
+     * @param src        The sequence of char values to normalize.
+     * @param form       The normalization form; one of
+     *                   {@link java.text.Normalizer.Form#NFC},
+     *                   {@link java.text.Normalizer.Form#NFD},
+     *                   {@link java.text.Normalizer.Form#NFKC},
+     *                   {@link java.text.Normalizer.Form#NFKD}
      * @return The normalized String
      * @throws NullPointerException If <code>src</code> or <code>form</code>
-     *                              is null.
+     * is null.
      */
     public static String normalize(CharSequence src, Form form) {
+        // Android-changed: Switched to ICU.
         return android.icu.text.Normalizer.normalize(src.toString(), form.icuMode);
     }
 
     /**
      * Determines if the given sequence of char values is normalized.
-     *
-     * @param src  The sequence of char values to be checked.
-     * @param form The normalization form; one of
-     *             {@link java.text.Normalizer.Form#NFC},
-     *             {@link java.text.Normalizer.Form#NFD},
-     *             {@link java.text.Normalizer.Form#NFKC},
-     *             {@link java.text.Normalizer.Form#NFKD}
+     * @param src        The sequence of char values to be checked.
+     * @param form       The normalization form; one of
+     *                   {@link java.text.Normalizer.Form#NFC},
+     *                   {@link java.text.Normalizer.Form#NFD},
+     *                   {@link java.text.Normalizer.Form#NFKC},
+     *                   {@link java.text.Normalizer.Form#NFKD}
      * @return true if the sequence of char values is normalized;
      * false otherwise.
      * @throws NullPointerException If <code>src</code> or <code>form</code>
-     *                              is null.
+     * is null.
      */
     public static boolean isNormalized(CharSequence src, Form form) {
+        // Android-changed: Switched to ICU.
         return android.icu.text.Normalizer.isNormalized(src.toString(), form.icuMode, 0);
     }
 }
diff --git a/java/text/NumberFormat.java b/java/text/NumberFormat.java
index 218a668..70a0aa9 100644
--- a/java/text/NumberFormat.java
+++ b/java/text/NumberFormat.java
@@ -286,10 +286,13 @@
      * @see java.text.Format#format
      */
     public final String format(double number) {
+        // Android-removed: fast-path code.
         return format(number, new StringBuffer(),
                       DontCareFieldPosition.INSTANCE).toString();
     }
 
+    // Android-removed: fastFormat method.
+
    /**
      * Specialization of format.
      *
@@ -545,6 +548,8 @@
         return getInstance(inLocale, PERCENTSTYLE);
     }
 
+    // Android-removed: non-API methods getScientificInstance([Locale]).
+
     // Android-changed: Removed reference to NumberFormatProvider.
     /**
      * Returns an array of all locales for which the
@@ -912,6 +917,7 @@
         stream.defaultWriteObject();
     }
 
+    // Android-added: cachedLocaleData.
     /**
      * Cache to hold the NumberPatterns of a Locale.
      */
@@ -921,6 +927,8 @@
     private static final int NUMBERSTYLE = 0;
     private static final int CURRENCYSTYLE = 1;
     private static final int PERCENTSTYLE = 2;
+    // Android-changed: changed: removed SCIENTIFICSTYLE and pull down INTEGERSTYLE value.
+    //private static final int SCIENTIFICSTYLE = 3;
     private static final int INTEGERSTYLE = 3;
 
     /**
diff --git a/java/text/RuleBasedCollator.java b/java/text/RuleBasedCollator.java
index a82080b..3f3c5bc 100644
--- a/java/text/RuleBasedCollator.java
+++ b/java/text/RuleBasedCollator.java
@@ -242,10 +242,12 @@
  * @see        CollationElementIterator
  * @author     Helena Shih, Laura Werner, Richard Gillam
  */
-public class RuleBasedCollator extends Collator {
+public class RuleBasedCollator extends Collator{
+    // Android-added: protected constructor taking an ICU RuleBasedCollator.
     RuleBasedCollator(android.icu.text.RuleBasedCollator wrapper) {
         super(wrapper);
     }
+
     // IMPLEMENTATION NOTES:  The implementation of the collation algorithm is
     // divided across three classes: RuleBasedCollator, RBCollationTables, and
     // CollationElementIterator.  RuleBasedCollator contains the collator's
@@ -280,6 +282,7 @@
      * throw the ParseException because the '?' is not quoted.
      */
     public RuleBasedCollator(String rules) throws ParseException {
+        // BEGIN Android-changed: Switched to ICU.
         if (rules == null) {
             throw new NullPointerException("rules == null");
         }
@@ -295,8 +298,12 @@
              */
             throw new ParseException(e.getMessage(), -1);
         }
+        // BEGIN Android-changed: Switched to ICU.
     }
 
+    // Android-removed: (String rules, int decomp) constructor and copy constructor.
+
+    // Android-changed: document that getRules() won't return rules in common case.
     /**
      * Gets the table-based rules for the collation object.
      *
@@ -308,6 +315,7 @@
      */
     public String getRules()
     {
+        // Android-changed: Switched to ICU.
         return collAsICU().getRules();
     }
 
@@ -319,6 +327,7 @@
      * @see java.text.CollationElementIterator
      */
     public CollationElementIterator getCollationElementIterator(String source) {
+        // Android-changed: Switch to ICU and check for null value.
         if (source == null) {
             throw new NullPointerException("source == null");
         }
@@ -335,6 +344,7 @@
      */
     public CollationElementIterator getCollationElementIterator(
                                                 CharacterIterator source) {
+        // Android-changed: Switch to ICU and check for null value.
        if (source == null) {
             throw new NullPointerException("source == null");
         }
@@ -354,6 +364,7 @@
         if (source == null || target == null) {
             throw new NullPointerException();
         }
+        // Android-changed: Switched to ICU.
         return icuColl.compare(source, target);
     }
 
@@ -364,6 +375,7 @@
      */
     public synchronized CollationKey getCollationKey(String source)
     {
+        // Android-changed: Switched to ICU.
         if (source == null) {
             return null;
         }
@@ -374,6 +386,7 @@
      * Standard override; no change in semantics.
      */
     public Object clone() {
+        // Android-changed: remove special case for cloning.
         return super.clone();
     }
 
@@ -385,6 +398,7 @@
      */
     public boolean equals(Object obj) {
         if (obj == null) return false;
+        // Android-changed: delegate to super class, as that already compares icuColl.
         return super.equals(obj);
     }
 
@@ -392,10 +406,14 @@
      * Generates the hash code for the table-based collation object
      */
     public int hashCode() {
+        // Android-changed: Switched to ICU.
         return icuColl.hashCode();
     }
 
+    // Android-added: collAsIcu helper method.
     private android.icu.text.RuleBasedCollator collAsICU() {
         return (android.icu.text.RuleBasedCollator) icuColl;
     }
+
+    // Android-removed: private constants and fields.
 }
diff --git a/java/text/SimpleDateFormat.java b/java/text/SimpleDateFormat.java
index 6ae9057..c294e5a 100644
--- a/java/text/SimpleDateFormat.java
+++ b/java/text/SimpleDateFormat.java
@@ -128,7 +128,7 @@
  *         <td>Week year
  *         <td><a href="#year">Year</a>
  *         <td><code>2009</code>; <code>09</code>
- *         <td>1+</td>
+ *         <td>24+</td>
  *     <tr style="background-color: rgb(238, 238, 255);">
  *         <td><code>M</code>
  *         <td>Month in year (context sensitive)
@@ -242,7 +242,7 @@
  *         <td>Time zone
  *         <td><a href="#iso8601timezone">ISO 8601 time zone</a>
  *         <td><code>-08</code>; <code>-0800</code>;  <code>-08:00</code>
- *         <td>1+</td>
+ *         <td>24+</td>
  * </table>
  * </blockquote>
  * Pattern letters are usually repeated, as their number determines the
@@ -559,6 +559,7 @@
      */
     transient boolean useDateFormatSymbols;
 
+    // Android-added: ICU TimeZoneNames field.
     /**
      * ICU TimeZoneNames used to format and parse time zone names.
      */
@@ -656,6 +657,7 @@
         // initialize calendar and related fields
         initializeCalendar(loc);
 
+        // BEGIN Android-changed: Use ICU for locale data.
         formatData = DateFormatSymbols.getInstanceRef(loc);
         LocaleData localeData = LocaleData.get(loc);
         if ((timeStyle >= 0) && (dateStyle >= 0)) {
@@ -674,6 +676,7 @@
         else {
             throw new IllegalArgumentException("No date or time style specified");
         }
+        // END Android-changed: Use ICU for locale data.
 
         initialize(loc);
     }
@@ -1072,8 +1075,8 @@
         CalendarBuilder.WEEK_YEAR,         // Pseudo Calendar field
         CalendarBuilder.ISO_DAY_OF_WEEK,   // Pseudo Calendar field
         Calendar.ZONE_OFFSET,
-        // 'L' and 'c',
         Calendar.MONTH,
+        // Android-added: 'c' for standalone day of week.
         Calendar.DAY_OF_WEEK
     };
 
@@ -1101,8 +1104,8 @@
         DateFormat.YEAR_FIELD,
         DateFormat.DAY_OF_WEEK_FIELD,
         DateFormat.TIMEZONE_FIELD,
-        // 'L' and 'c'
         DateFormat.MONTH_FIELD,
+        // Android-added: 'c' for standalone day of week.
         DateFormat.DAY_OF_WEEK_FIELD
     };
 
@@ -1130,11 +1133,12 @@
         Field.YEAR,
         Field.DAY_OF_WEEK,
         Field.TIME_ZONE,
-        // 'L' and 'c'
         Field.MONTH,
+        // Android-added: 'c' for standalone day of week.
         Field.DAY_OF_WEEK
     };
 
+    // BEGIN Android-added: Special handling for UTC time zone.
     private static final String UTC = "UTC";
 
     /**
@@ -1144,6 +1148,7 @@
     private static final Set<String> UTC_ZONE_IDS = Collections.unmodifiableSet(new HashSet<>(
             Arrays.asList("Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "UCT", "UTC",
                     "Universal", "Zulu")));
+    // END Android-added: Special handling for UTC time zone.
 
     /**
      * Private member function that does the real date/time formatting.
@@ -1238,15 +1243,19 @@
 
         case PATTERN_DAY_OF_WEEK: // 'E'
             if (current == null) {
+                // Android-changed: extract formatWeekday() method.
                 current = formatWeekday(count, value, useDateFormatSymbols, false /* standalone */);
             }
             break;
 
+        // BEGIN Android-added: support for 'c' (standalone day of week).
         case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
             if (current == null) {
+                // Android-changed: extract formatWeekday() method.
                 current = formatWeekday(count, value, useDateFormatSymbols, true /* standalone */);
             }
             break;
+        // END Android-added: support for 'c' (standalone day of week).
 
         case PATTERN_AM_PM:    // 'a'
             if (useDateFormatSymbols) {
@@ -1268,6 +1277,7 @@
 
         case PATTERN_ZONE_NAME: // 'z'
             if (current == null) {
+                // BEGIN Android-changed: format time zone name using ICU.
                 TimeZone tz = calendar.getTimeZone();
                 boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
                 String zoneString;
@@ -1279,7 +1289,8 @@
                             formatData.getZoneStringsWrapper(), tz.getID(), daylight, tzstyle);
                 } else {
                     if (UTC_ZONE_IDS.contains(tz.getID())) {
-                        // ICU doesn't have name strings for UTC, explicitly print it as "UTC".
+                        // ICU used to not have name strings UTC, explicitly print it as "UTC".
+                        // TODO: remove special case now that ICU has that data (http://b/36337342).
                         zoneString = UTC;
                     } else {
                         TimeZoneNames.NameType nameType;
@@ -1304,10 +1315,12 @@
                         calendar.get(Calendar.DST_OFFSET);
                     buffer.append(TimeZone.createGmtOffsetString(true, true, offsetMillis));
                 }
+                // END Android-changed: format time zone name using ICU.
             }
             break;
 
         case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
+        // BEGIN Android-Changed: use shared code in TimeZone for zone offset string.
         {
             value = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
             final boolean includeSeparator = (count >= 4);
@@ -1316,6 +1329,7 @@
 
             break;
         }
+        // END Android-Changed: use shared code in TimeZone for zone offset string.
 
         case PATTERN_ISO_ZONE:   // 'X'
             value = calendar.get(Calendar.ZONE_OFFSET)
@@ -1344,6 +1358,7 @@
             }
             CalendarUtils.sprintf0d(buffer, value % 60, 2);
             break;
+        // BEGIN Android-added: Better UTS#35 conformity for fractional seconds.
         case PATTERN_MILLISECOND: // 'S'
             // Fractional seconds must be treated specially. We must always convert the parsed
             // value into a fractional second [0, 1) and then widen it out to the appropriate
@@ -1355,12 +1370,15 @@
                 zeroPaddingNumber(value, count, count, buffer);
             }
             break;
+        // END Android-added: Better UTS#35 conformity for fractional seconds.
 
         default:
      // case PATTERN_DAY_OF_MONTH:         // 'd'
      // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
      // case PATTERN_MINUTE:               // 'm'
      // case PATTERN_SECOND:               // 's'
+     // Android-removed: PATTERN_MILLISECONDS handled in an explicit case above.
+     //// case PATTERN_MILLISECOND:          // 'S'
      // case PATTERN_DAY_OF_YEAR:          // 'D'
      // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
      // case PATTERN_WEEK_OF_YEAR:         // 'w'
@@ -1383,6 +1401,7 @@
         delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
     }
 
+    // BEGIN Android-added: formatWeekday and formatMonth methods to format using ICU data.
     private String formatWeekday(int count, int value, boolean useDateFormatSymbols,
                                  boolean standalone) {
         if (useDateFormatSymbols) {
@@ -1433,6 +1452,7 @@
 
         return current;
     }
+    // END Android-added: formatWeekday and formatMonth methods to format using ICU data.
 
     /**
      * Formats a number with the specified minimum and maximum number of digits.
@@ -1521,6 +1541,7 @@
      */
     @Override
     public Date parse(String text, ParsePosition pos) {
+        // BEGIN Android-changed: extract parseInternal() and avoid modifying timezone during parse.
         // Make sure the timezone associated with this dateformat instance (set via
         // {@code setTimeZone} isn't change as a side-effect of parsing a date.
         final TimeZone tz = getTimeZone();
@@ -1533,6 +1554,7 @@
 
     private Date parseInternal(String text, ParsePosition pos)
     {
+        // END Android-changed: extract parseInternal() and avoid modifying timezone during parse.
         checkNegativeNumberExpression();
 
         int start = pos.index;
@@ -1685,6 +1707,7 @@
                 bestMatchLength = length;
             }
 
+            // BEGIN Android-changed: Handle abbreviated fields that end with a '.'.
             // When the input option ends with a period (usually an abbreviated form), attempt
             // to match all chars up to that period.
             if ((data[i].charAt(length - 1) == '.') &&
@@ -1693,6 +1716,7 @@
                 bestMatch = i;
                 bestMatchLength = (length - 1);
             }
+            // END Android-changed: Handle abbreviated fields that end with a '.'.
         }
         if (bestMatch >= 0)
         {
@@ -1742,6 +1766,12 @@
         return -1;
     }
 
+    // Android-removed: unused private method matchDSTString.
+
+    // BEGIN Android-changed: Parse time zone strings using ICU TimeZoneNames.
+    // Note that this change falls back to the upstream zone names parsing code if the zoneStrings
+    // for the formatData field has been set by the user. The original code of subParseZoneString
+    // can be found in subParseZoneStringFromSymbols().
     /**
      * Parses the string in {@code text} (starting at {@code start}), interpreting it as a time zone
      * name. If a time zone is found, the internal calendar is set to that timezone and the index of
@@ -1863,6 +1893,7 @@
      * Parses the time zone string using the information in {@link #formatData}.
      */
     private int subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb) {
+        // END Android-changed: Parse time zone strings using ICU TimeZoneNames.
         boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
         TimeZone currentTimeZone = getTimeZone();
 
@@ -1967,9 +1998,9 @@
             if (count != 1) {
                 // Proceed with parsing mm
                 c = text.charAt(index++);
-                // Intentional change in behavior from OpenJDK. OpenJDK will return an error code
-                // if a : is found and colonRequired is false, this will return an error code if
-                // a : is not found and colonRequired is true.
+                // BEGIN Android-changed: Intentional change in behavior from OpenJDK.
+                // OpenJDK will return an error code if a : is found and colonRequired is false,
+                // this will return an error code if a : is not found and colonRequired is true.
                 //
                 // colonRequired | c == ':' | OpenJDK | this
                 //   false       |  false   |   ok    |  ok
@@ -1981,6 +2012,7 @@
                 } else if (colonRequired) {
                     break parse;
                 }
+                // END Android-changed: Intentional change in behavior from OpenJDK.
                 if (!isDigit(c)) {
                     break parse;
                 }
@@ -2152,6 +2184,7 @@
                 return pos.index;
 
             case PATTERN_MONTH: // 'M'
+            // BEGIN Android-changed: extract parseMonth method.
             {
                 final int idx = parseMonth(text, count, value, start, field, pos,
                         useDateFormatSymbols, false /* isStandalone */, calb);
@@ -2171,6 +2204,7 @@
                 }
                 break parsing;
             }
+            // END Android-changed: extract parseMonth method.
 
             case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
                 if (!isLenient()) {
@@ -2187,6 +2221,7 @@
                 return pos.index;
 
             case PATTERN_DAY_OF_WEEK:  // 'E'
+            // BEGIN Android-changed: extract parseWeekday method.
             {
                 final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
                         false /* standalone */, calb);
@@ -2195,7 +2230,9 @@
                 }
                 break parsing;
             }
+            // END Android-changed: extract parseWeekday method.
 
+            // BEGIN Android-added: support for 'c' (standalone day of week).
             case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
             {
                 final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
@@ -2206,6 +2243,7 @@
 
                 break parsing;
             }
+            // END Android-added: support for 'c' (standalone day of week).
 
             case PATTERN_AM_PM:    // 'a'
                 if (useDateFormatSymbols) {
@@ -2267,7 +2305,7 @@
                                         .set(Calendar.DST_OFFSET, 0);
                                     return pos.index;
                                 }
-
+                                // Android-changed: tolerate colon in zone offset.
                                 // Parse the rest as "hh[:]?mm"
                                 int i = subParseNumericZone(text, ++pos.index, sign, 0,
                                         false, calb);
@@ -2285,6 +2323,7 @@
                                 pos.index = -i;
                             }
                         } else {
+                            // Android-changed: tolerate colon in zone offset.
                             // Parse the rest as "hh[:]?mm" (RFC 822)
                             int i = subParseNumericZone(text, ++pos.index, sign, 0,
                                     false, calb);
@@ -2353,6 +2392,7 @@
                     number = numberFormat.parse(text, pos);
                 }
                 if (number != null) {
+                    // BEGIN Android-changed: Better UTS#35 conformity for fractional seconds.
                     if (patternCharIndex == PATTERN_MILLISECOND) {
                         // Fractional seconds must be treated specially. We must always
                         // normalize them to their fractional second value [0, 1) before we attempt
@@ -2367,6 +2407,7 @@
                     } else {
                         value = number.intValue();
                     }
+                    // END Android-changed: Better UTS#35 conformity for fractional seconds.
 
                     if (useFollowingMinusSignAsDelimiter && (value < 0) &&
                         (((pos.index < text.length()) &&
@@ -2389,6 +2430,7 @@
         return -1;
     }
 
+    // BEGIN Android-added: parseMonth and parseWeekday methods to parse using ICU data.
     private int parseMonth(String text, int count, int value, int start,
                            int field, ParsePosition pos, boolean useDateFormatSymbols,
                            boolean standalone,
@@ -2464,7 +2506,7 @@
 
         return index;
     }
-
+    // END Android-added: parseMonth and parseWeekday methods to parse using ICU data.
 
     private final String getCalendarName() {
         return calendar.getClass().getName();
diff --git a/java/time/format/DateTimeFormatterBuilder.java b/java/time/format/DateTimeFormatterBuilder.java
index f9cff0a..69e35d6 100644
--- a/java/time/format/DateTimeFormatterBuilder.java
+++ b/java/time/format/DateTimeFormatterBuilder.java
@@ -3641,7 +3641,7 @@
         private static final int DST = 1;
         private static final int GENERIC = 2;
 
-        // Android-changed: List of types used by getDisplayName().
+        // BEGIN Android-added: Lists of types used by getDisplayName().
         private static final TimeZoneNames.NameType[] TYPES = new TimeZoneNames.NameType[] {
                 TimeZoneNames.NameType.LONG_STANDARD,
                 TimeZoneNames.NameType.SHORT_STANDARD,
@@ -3662,6 +3662,7 @@
                 TimeZoneNames.NameType.SHORT_DAYLIGHT,
                 TimeZoneNames.NameType.SHORT_GENERIC,
         };
+        // END Android-added: Lists of types used by getDisplayName().
 
         private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
             new ConcurrentHashMap<>();
@@ -3675,7 +3676,7 @@
             Map<Locale, String[]> perLocale = null;
             if (ref == null || (perLocale = ref.get()) == null ||
                     (names = perLocale.get(locale)) == null) {
-                // Android-changed: use ICU TimeZoneNames instead of TimeZoneNameUtility.
+                // BEGIN Android-changed: use ICU TimeZoneNames instead of TimeZoneNameUtility.
                 TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
                 names = new String[TYPES.length + 1];
                 // Zeroth index used for id, other indexes based on NameType constant + 1.
@@ -3703,6 +3704,7 @@
                 if (names[5] == null) {
                     names[5] = names[0]; // use the id
                 }
+                // END Android-changed: use ICU TimeZoneNames instead of TimeZoneNameUtility.
                 if (names[6] == null) {
                     names[6] = names[0];
                 }
@@ -3763,12 +3765,12 @@
                 isCaseSensitive ? cachedTree : cachedTreeCI;
 
             Entry<Integer, SoftReference<PrefixTree>> entry;
+            // BEGIN Android-changed: use ICU TimeZoneNames to get Zone names.
             PrefixTree tree;
             if ((entry = cached.get(locale)) == null ||
                 (entry.getKey() != regionIdsSize ||
                 (tree = entry.getValue().get()) == null)) {
                 tree = PrefixTree.newTree(context);
-                // Android-changed: use ICU TimeZoneNames to get Zone names.
                 TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
                 long now = System.currentTimeMillis();
                 TimeZoneNames.NameType[] types =
@@ -3798,6 +3800,7 @@
                                 tree.add(names[i], zid);
                             }
                         }
+                        // END Android-changed: use ICU TimeZoneNames to get Zone names.
                     }
                 }
                 cached.put(locale, new SimpleImmutableEntry<>(regionIdsSize, new SoftReference<>(tree)));
@@ -3923,7 +3926,7 @@
                 return position;
             }
 
-            // Android-changed: "GMT0" is considered a valid ZoneId.
+            // Android-added: "GMT0" is considered a valid ZoneId.
             if (text.charAt(position) == '0' && prefix.equals("GMT")) {
                 context.setParsed(ZoneId.of("GMT0"));
                 return position + 1;
diff --git a/java/time/format/DateTimeTextProvider.java b/java/time/format/DateTimeTextProvider.java
index 99ab9b0..2ef9df8 100644
--- a/java/time/format/DateTimeTextProvider.java
+++ b/java/time/format/DateTimeTextProvider.java
@@ -448,6 +448,7 @@
         return "";  // null marker for map
     }
 
+    // Android-added: extractQuarters to extract Map of quarter names from ICU resource bundle.
     private static Map<Long, String> extractQuarters(ICUResourceBundle rb, String key) {
         String[] names = rb.getWithFallback(key).getStringArray();
         Map<Long, String> map = new HashMap<>();
@@ -468,7 +469,7 @@
         return new SimpleImmutableEntry<>(text, field);
     }
 
-    // Android-changed: removed getLocalizedResource.
+    // Android-removed: unused helper method getLocalizedResource.
 
     /**
      * Stores the text for a single locale.
diff --git a/java/time/format/ZoneName.java b/java/time/format/ZoneName.java
index c3eb20a..fe4a95a 100644
--- a/java/time/format/ZoneName.java
+++ b/java/time/format/ZoneName.java
@@ -38,10 +38,10 @@
  * The zid<->metazone mappings are based on CLDR metaZones.xml.
  * The alias mappings are based on Link entries in tzdb data files.
  */
-// Android-changed: delegate to ICU.
 class ZoneName {
 
     public static String toZid(String zid, Locale locale) {
+        // Android-changed: delegate to ICU.
         TimeZoneNames tzNames = TimeZoneNames.getInstance(locale);
         if (tzNames.getAvailableMetaZoneIDs().contains(zid)) {
             // Compare TimeZoneFormat#getTargetRegion.
@@ -64,4 +64,7 @@
         }
         return zid;
     }
+
+    // Android-removed: zidMap and aliasMap containing zone id data.
+    // Android-removed: zidToMzone, mzoneToZid, mzoneToZidL, aliases and their initialization code.
 }
diff --git a/java/util/zip/ZipFile.java b/java/util/zip/ZipFile.java
index fbe366e..797f2c6 100644
--- a/java/util/zip/ZipFile.java
+++ b/java/util/zip/ZipFile.java
@@ -396,7 +396,9 @@
             case DEFLATED:
                 // MORE: Compute good size for inflater stream:
                 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack
-                if (size > 65536) size = 8192;
+                // Android-changed: Use 64k buffer size, performs better than 8k.
+                // if (size > 65536) size = 8192;
+                if (size > 65536) size = 65536;
                 if (size <= 0) size = 4096;
                 Inflater inf = getInflater();
                 InputStream is =
diff --git a/org/apache/harmony/security/PrivateKeyImpl.java b/org/apache/harmony/security/PrivateKeyImpl.java
deleted file mode 100644
index 47aceb3..0000000
--- a/org/apache/harmony/security/PrivateKeyImpl.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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 org.apache.harmony.security;
-
-import java.security.PrivateKey;
-
-/**
- * PrivateKeyImpl
- */
-public class PrivateKeyImpl implements PrivateKey {
-
-    /*
-     * @serial
-     */
-    private static final long serialVersionUID = 7776497482533790279L;
-
-    private String algorithm;
-
-    private byte[] encoding;
-
-    public PrivateKeyImpl(String algorithm) {
-        this.algorithm = algorithm;
-    }
-
-    public String getAlgorithm() {
-        return algorithm;
-    }
-
-    public String getFormat() {
-        return "PKCS#8";
-    }
-
-    public byte[] getEncoded() {
-
-        byte[] toReturn = new byte[encoding.length];
-        System.arraycopy(encoding, 0, toReturn, 0, encoding.length);
-
-        return toReturn;
-    }
-
-    public void setAlgorithm(String algorithm) {
-        this.algorithm = algorithm;
-    }
-
-    public void setEncoding(byte[] encoding) {
-        this.encoding = new byte[encoding.length];
-        System.arraycopy(encoding, 0, this.encoding, 0, encoding.length);
-    }
-
-}
diff --git a/org/apache/harmony/security/PublicKeyImpl.java b/org/apache/harmony/security/PublicKeyImpl.java
deleted file mode 100644
index dccc72d..0000000
--- a/org/apache/harmony/security/PublicKeyImpl.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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 org.apache.harmony.security;
-
-import java.security.PublicKey;
-
-
-/**
- * PublicKeyImpl
- */
-public class PublicKeyImpl implements PublicKey {
-
-    /**
-     * @serial
-     */
-    private static final long serialVersionUID = 7179022516819534075L;
-
-
-    private byte[] encoding;
-
-    private String algorithm;
-
-
-    public PublicKeyImpl(String algorithm) {
-        this.algorithm = algorithm;
-    }
-
-
-    public String getAlgorithm() {
-        return algorithm;
-    }
-
-
-    public String getFormat() {
-        return "X.509";
-    }
-
-
-    public byte[] getEncoded() {
-        byte[] result = new byte[encoding.length];
-        System.arraycopy(encoding, 0, result, 0, encoding.length);
-        return result;
-    }
-
-
-    public void setAlgorithm(String algorithm) {
-        this.algorithm = algorithm;
-    }
-
-
-    public void setEncoding(byte[] encoding) {
-        this.encoding = new byte[encoding.length];
-        System.arraycopy(encoding, 0, this.encoding, 0, encoding.length);
-    }
-}
-
diff --git a/org/apache/harmony/security/provider/crypto/CryptoProvider.java b/org/apache/harmony/security/provider/crypto/CryptoProvider.java
deleted file mode 100644
index ad5ac7d..0000000
--- a/org/apache/harmony/security/provider/crypto/CryptoProvider.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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 org.apache.harmony.security.provider.crypto;
-
-import java.security.Provider;
-
-/**
- * Implementation of Provider for SecureRandom. The implementation supports the
- * "SHA1PRNG" algorithm described in JavaTM Cryptography Architecture, API
- * Specification & Reference
- */
-
-public final class CryptoProvider extends Provider {
-
-    private static final long serialVersionUID = 7991202868423459598L;
-
-    /**
-     * Creates a Provider and puts parameters
-     */
-    public CryptoProvider() {
-        super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)");
-
-        put("SecureRandom.SHA1PRNG",
-                "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
-        put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
-    }
-}
diff --git a/org/apache/harmony/security/provider/crypto/SHA1Constants.java b/org/apache/harmony/security/provider/crypto/SHA1Constants.java
deleted file mode 100644
index fc6a847..0000000
--- a/org/apache/harmony/security/provider/crypto/SHA1Constants.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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.
- */
-/**
-* @author Yuri A. Kropachev
-* @version $Revision$
-*/
-
-
-package org.apache.harmony.security.provider.crypto;
-
-
-/**
- * This interface contains : <BR>
- * - a set of constant values, H0-H4, defined in "SECURE HASH STANDARD", FIPS PUB 180-2 ;<BR>
- * - implementation constant values to use in classes using SHA-1 algorithm.    <BR>
- */
-public final class SHA1Constants {
-    private SHA1Constants() {
-    }
-
-    /**
-     *  constant defined in "SECURE HASH STANDARD"
-     */
-    public static final int H0 = 0x67452301;
-
-
-    /**
-     *  constant defined in "SECURE HASH STANDARD"
-     */
-    public static final int H1 = 0xEFCDAB89;
-
-
-    /**
-     *  constant defined in "SECURE HASH STANDARD"
-     */
-    public static final int H2 = 0x98BADCFE;
-
-
-    /**
-     *  constant defined in "SECURE HASH STANDARD"
-     */
-    public static final int H3 = 0x10325476;
-
-
-    /**
-     *  constant defined in "SECURE HASH STANDARD"
-     */
-    public static final int H4 = 0xC3D2E1F0;
-
-
-    /**
-     * offset in buffer to store number of bytes in 0-15 word frame
-     */
-    public static final int BYTES_OFFSET = 81;
-
-
-    /**
-     * offset in buffer to store current hash value
-     */
-    public static final int HASH_OFFSET = 82;
-
-
-    /**
-     * # of bytes in H0-H4 words; <BR>
-     * in this implementation # is set to 20 (in general # varies from 1 to 20)
-     */
-    public static final int DIGEST_LENGTH = 20;
-}
diff --git a/org/apache/harmony/security/provider/crypto/SHA1Impl.java b/org/apache/harmony/security/provider/crypto/SHA1Impl.java
deleted file mode 100644
index 57b9005..0000000
--- a/org/apache/harmony/security/provider/crypto/SHA1Impl.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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.
- */
-/**
-* @author Yuri A. Kropachev
-* @version $Revision$
-*/
-
-
-package org.apache.harmony.security.provider.crypto;
-
-import static org.apache.harmony.security.provider.crypto.SHA1Constants.*;
-
-/**
- * This class contains methods providing SHA-1 functionality to use in classes. <BR>
- * The methods support the algorithm described in "SECURE HASH STANDARD", FIPS PUB 180-2, <BR>
- * "http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf"      <BR>
- * <BR>
- * The class contains two package level access methods, -
- * "void updateHash(int[], byte[], int, int)" and "void computeHash(int[])", -
- * performing the following operations. <BR>
- * <BR>
- * The "updateHash(..)" method appends new bytes to existing ones
- * within limit of a frame of 64 bytes (16 words).
- * Once a length of accumulated bytes reaches the limit
- * the "computeHash(int[])" method is invoked on the frame to compute updated hash,
- * and the number of bytes in the frame is set to 0.
- * Thus, after appending all bytes, the frame contain only those bytes
- * that were not used in computing final hash value yet. <BR>
- * <BR>
- * The "computeHash(..)" method generates a 160 bit hash value using
- * a 512 bit message stored in first 16 words of int[] array argument and
- * current hash value stored in five words, beginning HASH_OFFSET, of the array argument.
- * Computation is done according to SHA-1 algorithm. <BR>
- * <BR>
- * The resulting hash value replaces the previous hash value in the array;
- * original bits of the message are not preserved.
- */
-public class SHA1Impl {
-
-
-    /**
-     * The method generates a 160 bit hash value using
-     * a 512 bit message stored in first 16 words of int[] array argument and
-     * current hash value stored in five words, beginning OFFSET+1, of the array argument.
-     * Computation is done according to SHA-1 algorithm.
-     *
-     * The resulting hash value replaces the previous hash value in the array;
-     * original bits of the message are not preserved.
-     *
-     * No checks on argument supplied, that is,
-     * a calling method is responsible for such checks.
-     * In case of incorrect array passed to the method
-     * either NPE or IndexOutOfBoundException gets thrown by JVM.
-     *
-     * @params
-     *        arrW - integer array; arrW.length >= (BYTES_OFFSET+6); <BR>
-     *               only first (BYTES_OFFSET+6) words are used
-     */
-    static void computeHash(int[] arrW) {
-
-        int  a = arrW[HASH_OFFSET   ];
-        int  b = arrW[HASH_OFFSET +1];
-        int  c = arrW[HASH_OFFSET +2];
-        int  d = arrW[HASH_OFFSET +3];
-        int  e = arrW[HASH_OFFSET +4];
-
-        int temp;
-
-        // In this implementation the "d. For t = 0 to 79 do" loop
-        // is split into four loops. The following constants:
-        //     K = 5A827999   0 <= t <= 19
-        //     K = 6ED9EBA1  20 <= t <= 39
-        //     K = 8F1BBCDC  40 <= t <= 59
-        //     K = CA62C1D6  60 <= t <= 79
-        // are hex literals in the loops.
-
-        for ( int t = 16; t < 80 ; t++ ) {
-
-            temp  = arrW[t-3] ^ arrW[t-8] ^ arrW[t-14] ^ arrW[t-16];
-            arrW[t] = ( temp<<1 ) | ( temp>>>31 );
-        }
-
-        for ( int t = 0 ; t < 20 ; t++ ) {
-
-            temp = ( ( a<<5 ) | ( a>>>27 )   ) +
-                   ( ( b & c) | ((~b) & d)   ) +
-                   ( e + arrW[t] + 0x5A827999 ) ;
-            e = d;
-            d = c;
-            c = ( b<<30 ) | ( b>>>2 ) ;
-            b = a;
-            a = temp;
-        }
-        for ( int t = 20 ; t < 40 ; t++ ) {
-
-            temp = ((( a<<5 ) | ( a>>>27 ))) + (b ^ c ^ d) + (e + arrW[t] + 0x6ED9EBA1) ;
-            e = d;
-            d = c;
-            c = ( b<<30 ) | ( b>>>2 ) ;
-            b = a;
-            a = temp;
-        }
-        for ( int t = 40 ; t < 60 ; t++ ) {
-
-            temp = (( a<<5 ) | ( a>>>27 )) + ((b & c) | (b & d) | (c & d)) +
-                                                             (e + arrW[t] + 0x8F1BBCDC) ;
-            e = d;
-            d = c;
-            c = ( b<<30 ) | ( b>>>2 ) ;
-            b = a;
-            a = temp;
-        }
-        for ( int t = 60 ; t < 80 ; t++ ) {
-
-            temp = ((( a<<5 ) | ( a>>>27 ))) + (b ^ c ^ d) + (e + arrW[t] + 0xCA62C1D6) ;
-            e = d;
-            d = c;
-            c = ( b<<30 ) | ( b>>>2 ) ;
-            b = a;
-            a = temp;
-        }
-
-        arrW[HASH_OFFSET   ] += a;
-        arrW[HASH_OFFSET +1] += b;
-        arrW[HASH_OFFSET +2] += c;
-        arrW[HASH_OFFSET +3] += d;
-        arrW[HASH_OFFSET +4] += e;
-    }
-
-    /**
-     * The method appends new bytes to existing ones
-     * within limit of a frame of 64 bytes (16 words).
-     *
-     * Once a length of accumulated bytes reaches the limit
-     * the "computeHash(int[])" method is invoked on the array to compute updated hash,
-     * and the number of bytes in the frame is set to 0.
-     * Thus, after appending all bytes, the array contain only those bytes
-     * that were not used in computing final hash value yet.
-     *
-     * No checks on arguments passed to the method, that is,
-     * a calling method is responsible for such checks.
-     *
-     * @params
-     *        intArray  - int array containing bytes to which to append;
-     *                    intArray.length >= (BYTES_OFFSET+6)
-     * @params
-     *        byteInput - array of bytes to use for the update
-     * @params
-     *        from      - the offset to start in the "byteInput" array
-     * @params
-     *        to        - a number of the last byte in the input array to use,
-     *                that is, for first byte "to"==0, for last byte "to"==input.length-1
-     */
-    static void updateHash(int[] intArray, byte[] byteInput, int fromByte, int toByte) {
-
-        // As intArray contains a packed bytes
-        // the buffer's index is in the intArray[BYTES_OFFSET] element
-
-        int index = intArray[BYTES_OFFSET];
-        int i = fromByte;
-        int maxWord;
-        int nBytes;
-
-        int wordIndex = index >>2;
-        int byteIndex = index & 0x03;
-
-        intArray[BYTES_OFFSET] = ( index + toByte - fromByte + 1 ) & 077 ;
-
-        // In general case there are 3 stages :
-        // - appending bytes to non-full word,
-        // - writing 4 bytes into empty words,
-        // - writing less than 4 bytes in last word
-
-        if ( byteIndex != 0 ) {       // appending bytes in non-full word (as if)
-
-            for ( ; ( i <= toByte ) && ( byteIndex < 4 ) ; i++ ) {
-                intArray[wordIndex] |= ( byteInput[i] & 0xFF ) << ((3 - byteIndex)<<3) ;
-                byteIndex++;
-            }
-            if ( byteIndex == 4 ) {
-                wordIndex++;
-                if ( wordIndex == 16 ) {          // intArray is full, computing hash
-
-                    computeHash(intArray);
-                    wordIndex = 0;
-                }
-            }
-            if ( i > toByte ) {                 // all input bytes appended
-                return ;
-            }
-        }
-
-        // writing full words
-
-        maxWord = (toByte - i + 1) >> 2;           // # of remaining full words, may be "0"
-        for ( int k = 0; k < maxWord ; k++ ) {
-
-            intArray[wordIndex] = ( ((int) byteInput[i   ] & 0xFF) <<24 ) |
-                                  ( ((int) byteInput[i +1] & 0xFF) <<16 ) |
-                                  ( ((int) byteInput[i +2] & 0xFF) <<8  ) |
-                                  ( ((int) byteInput[i +3] & 0xFF)      )  ;
-            i += 4;
-            wordIndex++;
-
-            if ( wordIndex < 16 ) {     // buffer is not full yet
-                continue;
-            }
-            computeHash(intArray);      // buffer is full, computing hash
-            wordIndex = 0;
-        }
-
-        // writing last incomplete word
-        // after writing free byte positions are set to "0"s
-
-        nBytes = toByte - i +1;
-        if ( nBytes != 0 ) {
-
-            int w =  ((int) byteInput[i] & 0xFF) <<24 ;
-
-            if ( nBytes != 1 ) {
-                w |= ((int) byteInput[i +1] & 0xFF) <<16 ;
-                if ( nBytes != 2) {
-                    w |= ((int) byteInput[i +2] & 0xFF) <<8 ;
-                }
-            }
-            intArray[wordIndex] = w;
-        }
-
-        return ;
-    }
-
-}
diff --git a/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java b/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java
deleted file mode 100644
index 5c0e328..0000000
--- a/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java
+++ /dev/null
@@ -1,564 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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 org.apache.harmony.security.provider.crypto;
-
-import dalvik.system.BlockGuard;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.security.InvalidParameterException;
-import java.security.ProviderException;
-import java.security.SecureRandomSpi;
-import libcore.io.Streams;
-import libcore.util.EmptyArray;
-
-import static org.apache.harmony.security.provider.crypto.SHA1Constants.*;
-
-/**
- * This class extends the SecureRandomSpi class implementing all its abstract methods.
- *
- * <p>To generate pseudo-random bits, the implementation uses technique described in
- * the "Random Number Generator (RNG) algorithms" section, Appendix A,
- * JavaTM Cryptography Architecture, API Specification & Reference.
- */
-public class SHA1PRNG_SecureRandomImpl extends SecureRandomSpi implements Serializable {
-
-    private static final long serialVersionUID = 283736797212159675L;
-
-    private static FileInputStream devURandom;
-    static {
-        try {
-            devURandom = new FileInputStream(new File("/dev/urandom"));
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    // constants to use in expressions operating on bytes in int and long variables:
-    // END_FLAGS - final bytes in words to append to message;
-    //             see "ch.5.1 Padding the Message, FIPS 180-2"
-    // RIGHT1    - shifts to right for left half of long
-    // RIGHT2    - shifts to right for right half of long
-    // LEFT      - shifts to left for bytes
-    // MASK      - mask to select counter's bytes after shift to right
-
-    private static final int[] END_FLAGS = { 0x80000000, 0x800000, 0x8000, 0x80 };
-
-    private static final int[] RIGHT1 = { 0, 40, 48, 56 };
-
-    private static final int[] RIGHT2 = { 0, 8, 16, 24 };
-
-    private static final int[] LEFT = { 0, 24, 16, 8 };
-
-    private static final int[] MASK = { 0xFFFFFFFF, 0x00FFFFFF, 0x0000FFFF,
-            0x000000FF };
-
-    // HASHBYTES_TO_USE defines # of bytes returned by "computeHash(byte[])"
-    // to use to form byte array returning by the "nextBytes(byte[])" method
-    // Note, that this implementation uses more bytes than it is defined
-    // in the above specification.
-    private static final int HASHBYTES_TO_USE = 20;
-
-    // value of 16 defined in the "SECURE HASH STANDARD", FIPS PUB 180-2
-    private static final int FRAME_LENGTH = 16;
-
-    // miscellaneous constants defined in this implementation:
-    // COUNTER_BASE - initial value to set to "counter" before computing "nextBytes(..)";
-    //                note, that the exact value is not defined in STANDARD
-    // HASHCOPY_OFFSET   - offset for copy of current hash in "copies" array
-    // EXTRAFRAME_OFFSET - offset for extra frame in "copies" array;
-    //                     as the extra frame follows the current hash frame,
-    //                     EXTRAFRAME_OFFSET is equal to length of current hash frame
-    // FRAME_OFFSET      - offset for frame in "copies" array
-    // MAX_BYTES - maximum # of seed bytes processing which doesn't require extra frame
-    //             see (1) comments on usage of "seed" array below and
-    //             (2) comments in "engineNextBytes(byte[])" method
-    //
-    // UNDEFINED  - three states of engine; initially its state is "UNDEFINED"
-    // SET_SEED     call to "engineSetSeed"  sets up "SET_SEED" state,
-    // NEXT_BYTES   call to "engineNextByte" sets up "NEXT_BYTES" state
-
-    private static final int COUNTER_BASE = 0;
-
-    private static final int HASHCOPY_OFFSET = 0;
-
-    private static final int EXTRAFRAME_OFFSET = 5;
-
-    private static final int FRAME_OFFSET = 21;
-
-    private static final int MAX_BYTES = 48;
-
-    private static final int UNDEFINED = 0;
-
-    private static final int SET_SEED = 1;
-
-    private static final int NEXT_BYTES = 2;
-
-    private static SHA1PRNG_SecureRandomImpl myRandom;
-
-    // Structure of "seed" array:
-    // -  0-79 - words for computing hash
-    // - 80    - unused
-    // - 81    - # of seed bytes in current seed frame
-    // - 82-86 - 5 words, current seed hash
-    private transient int[] seed;
-
-    // total length of seed bytes, including all processed
-    private transient long seedLength;
-
-    // Structure of "copies" array
-    // -  0-4  - 5 words, copy of current seed hash
-    // -  5-20 - extra 16 words frame;
-    //           is used if final padding exceeds 512-bit length
-    // - 21-36 - 16 word frame to store a copy of remaining bytes
-    private transient int[] copies;
-
-    // ready "next" bytes; needed because words are returned
-    private transient byte[] nextBytes;
-
-    // index of used bytes in "nextBytes" array
-    private transient int nextBIndex;
-
-    // variable required according to "SECURE HASH STANDARD"
-    private transient long counter;
-
-    // contains int value corresponding to engine's current state
-    private transient int state;
-
-    // The "seed" array is used to compute both "current seed hash" and "next bytes".
-    //
-    // As the "SHA1" algorithm computes a hash of entire seed by splitting it into
-    // a number of the 512-bit length frames (512 bits = 64 bytes = 16 words),
-    // "current seed hash" is a hash (5 words, 20 bytes) for all previous full frames;
-    // remaining bytes are stored in the 0-15 word frame of the "seed" array.
-    //
-    // As for calculating "next bytes",
-    // both remaining bytes and "current seed hash" are used,
-    // to preserve the latter for following "setSeed(..)" commands,
-    // the following technique is used:
-    // - upon getting "nextBytes(byte[])" invoked, single or first in row,
-    //   which requires computing new hash, that is,
-    //   there is no more bytes remaining from previous "next bytes" computation,
-    //   remaining bytes are copied into the 21-36 word frame of the "copies" array;
-    // - upon getting "setSeed(byte[])" invoked, single or first in row,
-    //   remaining bytes are copied back.
-
-    /**
-     *  Creates object and sets implementation variables to their initial values
-     */
-    public SHA1PRNG_SecureRandomImpl() {
-
-        seed = new int[HASH_OFFSET + EXTRAFRAME_OFFSET];
-        seed[HASH_OFFSET] = H0;
-        seed[HASH_OFFSET + 1] = H1;
-        seed[HASH_OFFSET + 2] = H2;
-        seed[HASH_OFFSET + 3] = H3;
-        seed[HASH_OFFSET + 4] = H4;
-
-        seedLength = 0;
-        copies = new int[2 * FRAME_LENGTH + EXTRAFRAME_OFFSET];
-        nextBytes = new byte[DIGEST_LENGTH];
-        nextBIndex = HASHBYTES_TO_USE;
-        counter = COUNTER_BASE;
-        state = UNDEFINED;
-    }
-
-    /*
-     * The method invokes the SHA1Impl's "updateHash(..)" method
-     * to update current seed frame and
-     * to compute new intermediate hash value if the frame is full.
-     *
-     * After that it computes a length of whole seed.
-     */
-    private void updateSeed(byte[] bytes) {
-
-        // on call:   "seed" contains current bytes and current hash;
-        // on return: "seed" contains new current bytes and possibly new current hash
-        //            if after adding, seed bytes overfill its buffer
-        SHA1Impl.updateHash(seed, bytes, 0, bytes.length - 1);
-
-        seedLength += bytes.length;
-    }
-
-    /**
-     * Changes current seed by supplementing a seed argument to the current seed,
-     * if this already set;
-     * the argument is used as first seed otherwise. <BR>
-     *
-     * The method overrides "engineSetSeed(byte[])" in class SecureRandomSpi.
-     *
-     * @param
-     *       seed - byte array
-     * @throws
-     *       NullPointerException - if null is passed to the "seed" argument
-     */
-    protected synchronized void engineSetSeed(byte[] seed) {
-
-        if (seed == null) {
-            throw new NullPointerException("seed == null");
-        }
-
-        if (state == NEXT_BYTES) { // first setSeed after NextBytes; restoring hash
-            System.arraycopy(copies, HASHCOPY_OFFSET, this.seed, HASH_OFFSET,
-                    EXTRAFRAME_OFFSET);
-        }
-        state = SET_SEED;
-
-        if (seed.length != 0) {
-            updateSeed(seed);
-        }
-    }
-
-    /**
-     * Returns a required number of random bytes. <BR>
-     *
-     * The method overrides "engineGenerateSeed (int)" in class SecureRandomSpi. <BR>
-     *
-     * @param
-     *       numBytes - number of bytes to return; should be >= 0.
-     * @return
-     *       byte array containing bits in order from left to right
-     * @throws
-     *       InvalidParameterException - if numBytes < 0
-     */
-    protected synchronized byte[] engineGenerateSeed(int numBytes) {
-
-        byte[] myBytes; // byte[] for bytes returned by "nextBytes()"
-
-        if (numBytes < 0) {
-            throw new NegativeArraySizeException(Integer.toString(numBytes));
-        }
-        if (numBytes == 0) {
-            return EmptyArray.BYTE;
-        }
-
-        if (myRandom == null) {
-            myRandom = new SHA1PRNG_SecureRandomImpl();
-            myRandom.engineSetSeed(getRandomBytes(DIGEST_LENGTH));
-        }
-
-        myBytes = new byte[numBytes];
-        myRandom.engineNextBytes(myBytes);
-
-        return myBytes;
-    }
-
-    /**
-     * Writes random bytes into an array supplied.
-     * Bits in a byte are from left to right. <BR>
-     *
-     * To generate random bytes, the "expansion of source bits" method is used,
-     * that is,
-     * the current seed with a 64-bit counter appended is used to compute new bits.
-     * The counter is incremented by 1 for each 20-byte output. <BR>
-     *
-     * The method overrides engineNextBytes in class SecureRandomSpi.
-     *
-     * @param
-     *       bytes - byte array to be filled in with bytes
-     * @throws
-     *       NullPointerException - if null is passed to the "bytes" argument
-     */
-    protected synchronized void engineNextBytes(byte[] bytes) {
-
-        int i, n;
-
-        long bits; // number of bits required by Secure Hash Standard
-        int nextByteToReturn; // index of ready bytes in "bytes" array
-        int lastWord; // index of last word in frame containing bytes
-        final int extrabytes = 7;// # of bytes to add in order to computer # of 8 byte words
-
-        if (bytes == null) {
-            throw new NullPointerException("bytes == null");
-        }
-
-        lastWord = seed[BYTES_OFFSET] == 0 ? 0
-                : (seed[BYTES_OFFSET] + extrabytes) >> 3 - 1;
-
-        if (state == UNDEFINED) {
-
-            // no seed supplied by user, hence it is generated thus randomizing internal state
-            updateSeed(getRandomBytes(DIGEST_LENGTH));
-            nextBIndex = HASHBYTES_TO_USE;
-
-            // updateSeed(...) updates where the last word of the seed is, so we
-            // have to read it again.
-            lastWord = seed[BYTES_OFFSET] == 0 ? 0
-                    : (seed[BYTES_OFFSET] + extrabytes) >> 3 - 1;
-
-        } else if (state == SET_SEED) {
-
-            System.arraycopy(seed, HASH_OFFSET, copies, HASHCOPY_OFFSET,
-                    EXTRAFRAME_OFFSET);
-
-            // possible cases for 64-byte frame:
-            //
-            // seed bytes < 48      - remaining bytes are enough for all, 8 counter bytes,
-            //                        0x80, and 8 seedLength bytes; no extra frame required
-            // 48 < seed bytes < 56 - remaining 9 bytes are for 0x80 and 8 counter bytes
-            //                        extra frame contains only seedLength value at the end
-            // seed bytes > 55      - extra frame contains both counter's bytes
-            //                        at the beginning and seedLength value at the end;
-            //                        note, that beginning extra bytes are not more than 8,
-            //                        that is, only 2 extra words may be used
-
-            // no need to set to "0" 3 words after "lastWord" and
-            // more than two words behind frame
-            for (i = lastWord + 3; i < FRAME_LENGTH + 2; i++) {
-                seed[i] = 0;
-            }
-
-            bits = (seedLength << 3) + 64; // transforming # of bytes into # of bits
-
-            // putting # of bits into two last words (14,15) of 16 word frame in
-            // seed or copies array depending on total length after padding
-            if (seed[BYTES_OFFSET] < MAX_BYTES) {
-                seed[14] = (int) (bits >>> 32);
-                seed[15] = (int) (bits & 0xFFFFFFFF);
-            } else {
-                copies[EXTRAFRAME_OFFSET + 14] = (int) (bits >>> 32);
-                copies[EXTRAFRAME_OFFSET + 15] = (int) (bits & 0xFFFFFFFF);
-            }
-
-            nextBIndex = HASHBYTES_TO_USE; // skipping remaining random bits
-        }
-        state = NEXT_BYTES;
-
-        if (bytes.length == 0) {
-            return;
-        }
-
-        nextByteToReturn = 0;
-
-        // possibly not all of HASHBYTES_TO_USE bytes were used previous time
-        n = (HASHBYTES_TO_USE - nextBIndex) < (bytes.length - nextByteToReturn) ? HASHBYTES_TO_USE
-                - nextBIndex
-                : bytes.length - nextByteToReturn;
-        if (n > 0) {
-            System.arraycopy(nextBytes, nextBIndex, bytes, nextByteToReturn, n);
-            nextBIndex += n;
-            nextByteToReturn += n;
-        }
-
-        if (nextByteToReturn >= bytes.length) {
-            return; // return because "bytes[]" are filled in
-        }
-
-        n = seed[BYTES_OFFSET] & 0x03;
-        for (;;) {
-            if (n == 0) {
-
-                seed[lastWord] = (int) (counter >>> 32);
-                seed[lastWord + 1] = (int) (counter & 0xFFFFFFFF);
-                seed[lastWord + 2] = END_FLAGS[0];
-
-            } else {
-
-                seed[lastWord] |= (int) ((counter >>> RIGHT1[n]) & MASK[n]);
-                seed[lastWord + 1] = (int) ((counter >>> RIGHT2[n]) & 0xFFFFFFFF);
-                seed[lastWord + 2] = (int) ((counter << LEFT[n]) | END_FLAGS[n]);
-            }
-            if (seed[BYTES_OFFSET] > MAX_BYTES) {
-                copies[EXTRAFRAME_OFFSET] = seed[FRAME_LENGTH];
-                copies[EXTRAFRAME_OFFSET + 1] = seed[FRAME_LENGTH + 1];
-            }
-
-            SHA1Impl.computeHash(seed);
-
-            if (seed[BYTES_OFFSET] > MAX_BYTES) {
-
-                System.arraycopy(seed, 0, copies, FRAME_OFFSET, FRAME_LENGTH);
-                System.arraycopy(copies, EXTRAFRAME_OFFSET, seed, 0,
-                        FRAME_LENGTH);
-
-                SHA1Impl.computeHash(seed);
-                System.arraycopy(copies, FRAME_OFFSET, seed, 0, FRAME_LENGTH);
-            }
-            counter++;
-
-            int j = 0;
-            for (i = 0; i < EXTRAFRAME_OFFSET; i++) {
-                int k = seed[HASH_OFFSET + i];
-                nextBytes[j] = (byte) (k >>> 24); // getting first  byte from left
-                nextBytes[j + 1] = (byte) (k >>> 16); // getting second byte from left
-                nextBytes[j + 2] = (byte) (k >>> 8); // getting third  byte from left
-                nextBytes[j + 3] = (byte) (k); // getting fourth byte from left
-                j += 4;
-            }
-
-            nextBIndex = 0;
-            j = HASHBYTES_TO_USE < (bytes.length - nextByteToReturn) ? HASHBYTES_TO_USE
-                    : bytes.length - nextByteToReturn;
-
-            if (j > 0) {
-                System.arraycopy(nextBytes, 0, bytes, nextByteToReturn, j);
-                nextByteToReturn += j;
-                nextBIndex += j;
-            }
-
-            if (nextByteToReturn >= bytes.length) {
-                break;
-            }
-        }
-    }
-
-    private void writeObject(ObjectOutputStream oos) throws IOException {
-
-        int[] intData = null;
-
-        final int only_hash = EXTRAFRAME_OFFSET;
-        final int hashes_and_frame = EXTRAFRAME_OFFSET * 2 + FRAME_LENGTH;
-        final int hashes_and_frame_extra = EXTRAFRAME_OFFSET * 2 + FRAME_LENGTH
-                * 2;
-
-        oos.writeLong(seedLength);
-        oos.writeLong(counter);
-        oos.writeInt(state);
-        oos.writeInt(seed[BYTES_OFFSET]);
-
-        int nRemaining = (seed[BYTES_OFFSET] + 3) >> 2; // converting bytes in words
-        // result may be 0
-        if (state != NEXT_BYTES) {
-
-            // either the state is UNDEFINED or previous method was "setSeed(..)"
-            // so in "seed[]" to serialize are remaining bytes (seed[0-nRemaining]) and
-            // current hash (seed[82-86])
-
-            intData = new int[only_hash + nRemaining];
-
-            System.arraycopy(seed, 0, intData, 0, nRemaining);
-            System.arraycopy(seed, HASH_OFFSET, intData, nRemaining,
-                    EXTRAFRAME_OFFSET);
-
-        } else {
-            // previous method was "nextBytes(..)"
-            // so, data to serialize are all the above (two first are in "copies" array)
-            // and current words in both frame and extra frame (as if)
-
-            int offset = 0;
-            if (seed[BYTES_OFFSET] < MAX_BYTES) { // no extra frame
-
-                intData = new int[hashes_and_frame + nRemaining];
-
-            } else { // extra frame is used
-
-                intData = new int[hashes_and_frame_extra + nRemaining];
-
-                intData[offset] = seed[FRAME_LENGTH];
-                intData[offset + 1] = seed[FRAME_LENGTH + 1];
-                intData[offset + 2] = seed[FRAME_LENGTH + 14];
-                intData[offset + 3] = seed[FRAME_LENGTH + 15];
-                offset += 4;
-            }
-
-            System.arraycopy(seed, 0, intData, offset, FRAME_LENGTH);
-            offset += FRAME_LENGTH;
-
-            System.arraycopy(copies, FRAME_LENGTH + EXTRAFRAME_OFFSET, intData,
-                    offset, nRemaining);
-            offset += nRemaining;
-
-            System.arraycopy(copies, 0, intData, offset, EXTRAFRAME_OFFSET);
-            offset += EXTRAFRAME_OFFSET;
-
-            System.arraycopy(seed, HASH_OFFSET, intData, offset,
-                    EXTRAFRAME_OFFSET);
-        }
-        for (int i = 0; i < intData.length; i++) {
-            oos.writeInt(intData[i]);
-        }
-
-        oos.writeInt(nextBIndex);
-        oos.write(nextBytes, nextBIndex, HASHBYTES_TO_USE - nextBIndex);
-    }
-
-    private void readObject(ObjectInputStream ois) throws IOException,
-            ClassNotFoundException {
-
-        seed = new int[HASH_OFFSET + EXTRAFRAME_OFFSET];
-        copies = new int[2 * FRAME_LENGTH + EXTRAFRAME_OFFSET];
-        nextBytes = new byte[DIGEST_LENGTH];
-
-        seedLength = ois.readLong();
-        counter = ois.readLong();
-        state = ois.readInt();
-        seed[BYTES_OFFSET] = ois.readInt();
-
-        int nRemaining = (seed[BYTES_OFFSET] + 3) >> 2; // converting bytes in words
-
-        if (state != NEXT_BYTES) {
-
-            for (int i = 0; i < nRemaining; i++) {
-                seed[i] = ois.readInt();
-            }
-            for (int i = 0; i < EXTRAFRAME_OFFSET; i++) {
-                seed[HASH_OFFSET + i] = ois.readInt();
-            }
-        } else {
-            if (seed[BYTES_OFFSET] >= MAX_BYTES) {
-
-                // reading next bytes in seed extra frame
-                seed[FRAME_LENGTH] = ois.readInt();
-                seed[FRAME_LENGTH + 1] = ois.readInt();
-                seed[FRAME_LENGTH + 14] = ois.readInt();
-                seed[FRAME_LENGTH + 15] = ois.readInt();
-            }
-            // reading next bytes in seed frame
-            for (int i = 0; i < FRAME_LENGTH; i++) {
-                seed[i] = ois.readInt();
-            }
-            // reading remaining seed bytes
-            for (int i = 0; i < nRemaining; i++) {
-                copies[FRAME_LENGTH + EXTRAFRAME_OFFSET + i] = ois.readInt();
-            }
-            // reading copy of current hash
-            for (int i = 0; i < EXTRAFRAME_OFFSET; i++) {
-                copies[i] = ois.readInt();
-            }
-            // reading current hash
-            for (int i = 0; i < EXTRAFRAME_OFFSET; i++) {
-                seed[HASH_OFFSET + i] = ois.readInt();
-            }
-        }
-
-        nextBIndex = ois.readInt();
-        Streams.readFully(ois, nextBytes, nextBIndex, HASHBYTES_TO_USE - nextBIndex);
-    }
-
-    private static byte[] getRandomBytes(int byteCount) {
-        if (byteCount <= 0) {
-            throw new IllegalArgumentException("Too few bytes requested: " + byteCount);
-        }
-
-        BlockGuard.Policy originalPolicy = BlockGuard.getThreadPolicy();
-        try {
-            BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
-            byte[] result = new byte[byteCount];
-            Streams.readFully(devURandom, result, 0, byteCount);
-            return result;
-        } catch (Exception ex) {
-            throw new ProviderException("Couldn't read " + byteCount + " random bytes", ex);
-        } finally {
-            BlockGuard.setThreadPolicy(originalPolicy);
-        }
-    }
-}