Import Android SDK Platform P [4456821]

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

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: I2d206b200d7952f899a5d1647ab532638cc8dd43
diff --git a/android/animation/ValueAnimator.java b/android/animation/ValueAnimator.java
index ee89ca8..cc95eb6 100644
--- a/android/animation/ValueAnimator.java
+++ b/android/animation/ValueAnimator.java
@@ -254,6 +254,11 @@
     HashMap<String, PropertyValuesHolder> mValuesMap;
 
     /**
+     * If set to non-negative value, this will override {@link #sDurationScale}.
+     */
+    private float mDurationScale = -1f;
+
+    /**
      * Public constants
      */
 
@@ -579,8 +584,23 @@
         return this;
     }
 
+    /**
+     * Overrides the global duration scale by a custom value.
+     *
+     * @param durationScale The duration scale to set; or {@code -1f} to use the global duration
+     *                      scale.
+     * @hide
+     */
+    public void overrideDurationScale(float durationScale) {
+        mDurationScale = durationScale;
+    }
+
+    private float resolveDurationScale() {
+        return mDurationScale >= 0f ? mDurationScale : sDurationScale;
+    }
+
     private long getScaledDuration() {
-        return (long)(mDuration * sDurationScale);
+        return (long)(mDuration * resolveDurationScale());
     }
 
     /**
@@ -735,7 +755,10 @@
         if (mSeekFraction >= 0) {
             return (long) (mDuration * mSeekFraction);
         }
-        float durationScale = sDurationScale == 0 ? 1 : sDurationScale;
+        float durationScale = resolveDurationScale();
+        if (durationScale == 0f) {
+            durationScale = 1f;
+        }
         return (long) ((AnimationUtils.currentAnimationTimeMillis() - mStartTime) / durationScale);
     }
 
@@ -1397,7 +1420,9 @@
         if (mStartTime < 0) {
             // First frame. If there is start delay, start delay count down will happen *after* this
             // frame.
-            mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
+            mStartTime = mReversing
+                    ? frameTime
+                    : frameTime + (long) (mStartDelay * resolveDurationScale());
         }
 
         // Handle pause/resume
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 9d331a0..99f3dee 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -16,8 +16,6 @@
 
 package android.app;
 
-import static android.os.Build.VERSION_CODES.O_MR1;
-
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.CallSuper;
@@ -136,6 +134,7 @@
 import java.util.HashMap;
 import java.util.List;
 
+
 /**
  * An activity is a single, focused thing that the user can do.  Almost all
  * activities interact with the user, so the Activity class takes care of
@@ -194,10 +193,13 @@
  * <a name="Fragments"></a>
  * <h3>Fragments</h3>
  *
- * <p>Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, Activity
- * implementations can make use of the {@link Fragment} class to better
+ * <p>The {@link android.support.v4.app.FragmentActivity} subclass
+ * can make use of the {@link android.support.v4.app.Fragment} class to better
  * modularize their code, build more sophisticated user interfaces for larger
- * screens, and help scale their application between small and large screens.
+ * screens, and help scale their application between small and large screens.</p>
+ *
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
  *
  * <a name="ActivityLifecycle"></a>
  * <h3>Activity Lifecycle</h3>
@@ -916,7 +918,10 @@
 
     /**
      * Return the LoaderManager for this activity, creating it if needed.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportLoaderManager()}
      */
+    @Deprecated
     public LoaderManager getLoaderManager() {
         return mFragments.getLoaderManager();
     }
@@ -991,17 +996,6 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
 
-        if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
-            final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
-            final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
-            ta.recycle();
-
-            if (isTranslucentOrFloating) {
-                throw new IllegalStateException(
-                        "Only fullscreen opaque activities can request orientation");
-            }
-        }
-
         if (mLastNonConfigurationInstances != null) {
             mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
         }
@@ -2407,7 +2401,10 @@
     /**
      * Return the FragmentManager for interacting with fragments associated
      * with this activity.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
      */
+    @Deprecated
     public FragmentManager getFragmentManager() {
         return mFragments.getFragmentManager();
     }
@@ -2416,7 +2413,11 @@
      * Called when a Fragment is being attached to this activity, immediately
      * after the call to its {@link Fragment#onAttach Fragment.onAttach()}
      * method and before {@link Fragment#onCreate Fragment.onCreate()}.
+     *
+     * @deprecated Use {@link
+     * android.support.v4.app.FragmentActivity#onAttachFragment(android.support.v4.app.Fragment)}
      */
+    @Deprecated
     public void onAttachFragment(Fragment fragment) {
     }
 
@@ -5118,7 +5119,11 @@
      *
      * @see Fragment#startActivity
      * @see Fragment#startActivityForResult
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+     * android.support.v4.app.Fragment,Intent,int)}
      */
+    @Deprecated
     public void startActivityFromFragment(@NonNull Fragment fragment,
             @RequiresPermission Intent intent, int requestCode) {
         startActivityFromFragment(fragment, intent, requestCode, null);
@@ -5143,7 +5148,11 @@
      *
      * @see Fragment#startActivity
      * @see Fragment#startActivityForResult
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+     * android.support.v4.app.Fragment,Intent,int,Bundle)}
      */
+    @Deprecated
     public void startActivityFromFragment(@NonNull Fragment fragment,
             @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
         startActivityForResult(fragment.mWho, intent, requestCode, options);
@@ -7304,24 +7313,25 @@
     }
 
     /**
-     * Request to put this Activity in a mode where the user is locked to the
-     * current task.
+     * Request to put this activity in a mode where the user is locked to a restricted set of
+     * applications.
      *
-     * This will prevent the user from launching other apps, going to settings, or reaching the
-     * home screen. This does not include those apps whose {@link android.R.attr#lockTaskMode}
-     * values permit launching while locked.
+     * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true}
+     * for this component, the current task will be launched directly into LockTask mode. Only apps
+     * whitelisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can
+     * be launched while LockTask mode is active. The user will not be able to leave this mode
+     * until this activity calls {@link #stopLockTask()}. Calling this method while the device is
+     * already in LockTask mode has no effect.
      *
-     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true or
-     * lockTaskMode=lockTaskModeAlways for this component then the app will go directly into
-     * Lock Task mode. The user will not be able to exit this mode until
-     * {@link Activity#stopLockTask()} is called.
+     * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the
+     * system will prompt the user with a dialog requesting permission to use this mode.
+     * The user can exit at any time through instructions shown on the request dialog. Calling
+     * {@link #stopLockTask()} will also terminate this mode.
      *
-     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
-     * then the system will prompt the user with a dialog requesting permission to enter
-     * this mode.  When entered through this method the user can exit at any time through
-     * an action described by the request dialog.  Calling stopLockTask will also exit the
-     * mode.
+     * <p><strong>Note:</strong> this method can only be called when the activity is foreground.
+     * That is, between {@link #onResume()} and {@link #onPause()}.
      *
+     * @see #stopLockTask()
      * @see android.R.attr#lockTaskMode
      */
     public void startLockTask() {
@@ -7332,25 +7342,24 @@
     }
 
     /**
-     * Allow the user to switch away from the current task.
+     * Stop the current task from being locked.
      *
-     * Called to end the mode started by {@link Activity#startLockTask}. This
-     * can only be called by activities that have successfully called
-     * startLockTask previously.
+     * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}.
+     * This can only be called by activities that have called {@link #startLockTask()} previously.
      *
-     * This will allow the user to exit this app and move onto other activities.
-     * <p>Note: This method should only be called when the activity is user-facing. That is,
-     * between onResume() and onPause().
-     * <p>Note: If there are other tasks below this one that are also locked then calling this
-     * method will immediately finish this task and resume the previous locked one, remaining in
-     * lockTask mode.
+     * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started
+     * by this activity, then calling this method will not terminate the LockTask mode, but only
+     * finish its own task. The device will remain in LockTask mode, until the activity which
+     * started the LockTask mode calls this method, or until its whitelist authorization is revoked
+     * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
      *
+     * @see #startLockTask()
      * @see android.R.attr#lockTaskMode
      * @see ActivityManager#getLockTaskModeState()
      */
     public void stopLockTask() {
         try {
-            ActivityManager.getService().stopLockTaskMode();
+            ActivityManager.getService().stopLockTaskModeByToken(mToken);
         } catch (RemoteException e) {
         }
     }
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 8d9dc1f..064e978 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -682,20 +682,23 @@
     }
 
     /**
-     * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
-     * specifies the position of the created docked stack at the top half of the screen if
+     * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+     * which specifies the position of the created docked stack at the top half of the screen if
      * in portrait mode or at the left half of the screen if in landscape mode.
      * @hide
      */
-    public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0;
+    @TestApi
+    public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
 
     /**
-     * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
+     * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+     * which
      * specifies the position of the created docked stack at the bottom half of the screen if
      * in portrait mode or at the right half of the screen if in landscape mode.
      * @hide
      */
-    public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
+    @TestApi
+    public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
 
     /**
      * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
@@ -1925,6 +1928,33 @@
     }
 
     /**
+     * Moves the input task to the primary-split-screen stack.
+     * @param taskId Id of task to move.
+     * @param createMode The mode the primary split screen stack should be created in if it doesn't
+     *                  exist already. See
+     *                   {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
+     *                   and
+     *                   {@link android.app.ActivityManager
+     *                        #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
+     * @param toTop If the task and stack should be moved to the top.
+     * @param animate Whether we should play an animation for the moving the task
+     * @param initialBounds If the primary stack gets created, it will use these bounds for the
+     *                      docked stack. Pass {@code null} to use default bounds.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+            boolean animate, Rect initialBounds) throws SecurityException {
+        try {
+            getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate,
+                    initialBounds);
+        } 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.
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
index 9d14f61..a46b3c7 100644
--- a/android/app/ActivityManagerInternal.java
+++ b/android/app/ActivityManagerInternal.java
@@ -64,6 +64,27 @@
     public static final int APP_TRANSITION_SNAPSHOT = 4;
 
     /**
+     * The bundle key to extract the assist data.
+     */
+    public static final String ASSIST_KEY_DATA = "data";
+
+    /**
+     * The bundle key to extract the assist structure.
+     */
+    public static final String ASSIST_KEY_STRUCTURE = "structure";
+
+    /**
+     * The bundle key to extract the assist content.
+     */
+    public static final String ASSIST_KEY_CONTENT = "content";
+
+    /**
+     * The bundle key to extract the assist receiver extras.
+     */
+    public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras";
+
+
+    /**
      * Grant Uri permissions from one app to another. This method only extends
      * permission grants if {@code callingUid} has permission to them.
      */
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index b62e4c2..4a21f5c 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -16,7 +16,7 @@
 
 package android.app;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
@@ -203,10 +203,11 @@
             "android.activity.taskOverlayCanResume";
 
     /**
-     * Where the docked stack should be positioned.
+     * Where the split-screen-primary stack should be positioned.
      * @hide
      */
-    private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
+    private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
+            "android:activity.splitScreenCreateMode";
 
     /**
      * Determines whether to disallow the outgoing activity from entering picture-in-picture as the
@@ -292,7 +293,7 @@
     @WindowConfiguration.ActivityType
     private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
     private int mLaunchTaskId = -1;
-    private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+    private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     private boolean mDisallowEnterPictureInPictureWhileLaunching;
     private boolean mTaskOverlay;
     private boolean mTaskOverlayCanResume;
@@ -884,7 +885,8 @@
         mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
         mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
         mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
-        mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+        mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
+                SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
         mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
                 KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
         if (opts.containsKey(KEY_ANIM_SPECS)) {
@@ -1194,13 +1196,13 @@
     }
 
     /** @hide */
-    public int getDockCreateMode() {
-        return mDockCreateMode;
+    public int getSplitScreenCreateMode() {
+        return mSplitScreenCreateMode;
     }
 
     /** @hide */
-    public void setDockCreateMode(int dockCreateMode) {
-        mDockCreateMode = dockCreateMode;
+    public void setSplitScreenCreateMode(int splitScreenCreateMode) {
+        mSplitScreenCreateMode = splitScreenCreateMode;
     }
 
     /** @hide */
@@ -1369,7 +1371,7 @@
         b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
         b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
         b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
-        b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+        b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
         b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
                 mDisallowEnterPictureInPictureWhileLaunching);
         if (mAnimSpecs != null) {
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 2516a3e..21e454f 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -5533,32 +5533,8 @@
         View.mDebugViewAttributes =
                 mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
 
-        /**
-         * For system applications on userdebug/eng builds, log stack
-         * traces of disk and network access to dropbox for analysis.
-         */
-        if ((data.appInfo.flags &
-             (ApplicationInfo.FLAG_SYSTEM |
-              ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
-            StrictMode.conditionallyEnableDebugLogging();
-        }
-
-        /**
-         * For apps targetting Honeycomb or later, we don't allow network usage
-         * on the main event loop / UI thread. This is what ultimately throws
-         * {@link NetworkOnMainThreadException}.
-         */
-        if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
-            StrictMode.enableDeathOnNetwork();
-        }
-
-        /**
-         * For apps targetting N or later, we don't allow file:// Uri exposure.
-         * This is what ultimately throws {@link FileUriExposedException}.
-         */
-        if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
-            StrictMode.enableDeathOnFileUriExposure();
-        }
+        StrictMode.initThreadDefaults(data.appInfo);
+        StrictMode.initVmDefaults(data.appInfo);
 
         // We deprecated Build.SERIAL and only apps that target pre NMR1
         // SDK can see it. Since access to the serial is now behind a
@@ -5655,7 +5631,12 @@
                 mResourcesManager.getConfiguration().getLocales());
 
         if (!Process.isIsolated()) {
-            setupGraphicsSupport(appContext);
+            final int oldMask = StrictMode.allowThreadDiskWritesMask();
+            try {
+                setupGraphicsSupport(appContext);
+            } finally {
+                StrictMode.setThreadPolicyMask(oldMask);
+            }
         }
 
         // If we use profiles, setup the dex reporter to notify package manager
diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java
index 4bd85ae..b6fb120 100644
--- a/android/app/AppOpsManager.java
+++ b/android/app/AppOpsManager.java
@@ -254,8 +254,10 @@
     public static final int OP_ANSWER_PHONE_CALLS = 69;
     /** @hide Run jobs when in background */
     public static final int OP_RUN_ANY_IN_BACKGROUND = 70;
+    /** @hide Change Wi-Fi connectivity state */
+    public static final int OP_CHANGE_WIFI_STATE = 71;
     /** @hide */
-    public static final int _NUM_OP = 71;
+    public static final int _NUM_OP = 72;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -496,6 +498,7 @@
             OP_INSTANT_APP_START_FOREGROUND,
             OP_ANSWER_PHONE_CALLS,
             OP_RUN_ANY_IN_BACKGROUND,
+            OP_CHANGE_WIFI_STATE,
     };
 
     /**
@@ -574,6 +577,7 @@
             OPSTR_INSTANT_APP_START_FOREGROUND,
             OPSTR_ANSWER_PHONE_CALLS,
             null, // OP_RUN_ANY_IN_BACKGROUND
+            null, // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -652,6 +656,7 @@
             "INSTANT_APP_START_FOREGROUND",
             "ANSWER_PHONE_CALLS",
             "RUN_ANY_IN_BACKGROUND",
+            "CHANGE_WIFI_STATE",
     };
 
     /**
@@ -730,6 +735,7 @@
             Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
             Manifest.permission.ANSWER_PHONE_CALLS,
             null, // no permission for OP_RUN_ANY_IN_BACKGROUND
+            Manifest.permission.CHANGE_WIFI_STATE,
     };
 
     /**
@@ -809,6 +815,7 @@
             null, // INSTANT_APP_START_FOREGROUND
             null, // ANSWER_PHONE_CALLS
             null, // OP_RUN_ANY_IN_BACKGROUND
+            null, // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -887,6 +894,7 @@
             false, // INSTANT_APP_START_FOREGROUND
             false, // ANSWER_PHONE_CALLS
             false, // OP_RUN_ANY_IN_BACKGROUND
+            false, // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -964,6 +972,7 @@
             AppOpsManager.MODE_DEFAULT,  // OP_INSTANT_APP_START_FOREGROUND
             AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
             AppOpsManager.MODE_ALLOWED,  // OP_RUN_ANY_IN_BACKGROUND
+            AppOpsManager.MODE_ALLOWED,  // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -1045,6 +1054,7 @@
             false,
             false, // ANSWER_PHONE_CALLS
             false, // OP_RUN_ANY_IN_BACKGROUND
+            false, // OP_CHANGE_WIFI_STATE
     };
 
     /**
diff --git a/android/app/DexLoadReporter.java b/android/app/DexLoadReporter.java
index f99d1a8..0643414 100644
--- a/android/app/DexLoadReporter.java
+++ b/android/app/DexLoadReporter.java
@@ -19,7 +19,6 @@
 import android.os.FileUtils;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.system.ErrnoException;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -27,8 +26,6 @@
 import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMRuntime;
 
-import libcore.io.Libcore;
-
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -155,23 +152,12 @@
             return;
         }
 
-        File realDexPath;
-        try {
-            // Secondary dex profiles are stored in the oat directory, next to the real dex file
-            // and have the same name with 'cur.prof' appended. We use the realpath because that
-            // is what installd is using when processing the dex file.
-            // NOTE: Keep in sync with installd.
-            realDexPath = new File(Libcore.os.realpath(dexPath));
-        } catch (ErrnoException ex) {
-            Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath
-                    + ":" + ex.getMessage());
-            // Do not continue with registration if we could not retrieve the real path.
-            return;
-        }
-
+        // Secondary dex profiles are stored in the oat directory, next to dex file
+        // and have the same name with 'cur.prof' appended.
         // NOTE: Keep this in sync with installd expectations.
-        File secondaryProfileDir = new File(realDexPath.getParent(), "oat");
-        File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof");
+        File dexPathFile = new File(dexPath);
+        File secondaryProfileDir = new File(dexPathFile.getParent(), "oat");
+        File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof");
 
         // Create the profile if not already there.
         // Returns true if the file was created, false if the file already exists.
diff --git a/android/app/DialogFragment.java b/android/app/DialogFragment.java
index 7e0e4d8..a0fb6ee 100644
--- a/android/app/DialogFragment.java
+++ b/android/app/DialogFragment.java
@@ -136,7 +136,10 @@
  *
  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
  *      embed}
+ *
+ * @deprecated Use {@link android.support.v4.app.DialogFragment}
  */
+@Deprecated
 public class DialogFragment extends Fragment
         implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
 
diff --git a/android/app/Fragment.java b/android/app/Fragment.java
index 9377345..a92684b 100644
--- a/android/app/Fragment.java
+++ b/android/app/Fragment.java
@@ -256,7 +256,10 @@
  * <p>After each call to this function, a new entry is on the stack, and
  * pressing back will pop it to return the user to whatever previous state
  * the activity UI was in.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment}
  */
+@Deprecated
 public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
     private static final ArrayMap<String, Class<?>> sClassMap =
             new ArrayMap<String, Class<?>>();
@@ -414,7 +417,10 @@
      * State information that has been retrieved from a fragment instance
      * through {@link FragmentManager#saveFragmentInstanceState(Fragment)
      * FragmentManager.saveFragmentInstanceState}.
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment.SavedState}
      */
+    @Deprecated
     public static class SavedState implements Parcelable {
         final Bundle mState;
 
@@ -458,7 +464,10 @@
     /**
      * Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when
      * there is an instantiation failure.
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment.InstantiationException}
      */
+    @Deprecated
     static public class InstantiationException extends AndroidRuntimeException {
         public InstantiationException(String msg, Exception cause) {
             super(msg, cause);
@@ -1031,7 +1040,10 @@
 
     /**
      * Return the LoaderManager for this fragment, creating it if needed.
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment#getLoaderManager()}
      */
+    @Deprecated
     public LoaderManager getLoaderManager() {
         if (mLoaderManager != null) {
             return mLoaderManager;
diff --git a/android/app/FragmentBreadCrumbs.java b/android/app/FragmentBreadCrumbs.java
index d0aa0fd..e3e47ae 100644
--- a/android/app/FragmentBreadCrumbs.java
+++ b/android/app/FragmentBreadCrumbs.java
@@ -65,7 +65,10 @@
 
     /**
      * Interface to intercept clicks on the bread crumbs.
+     *
+     * @deprecated This widget is no longer supported.
      */
+    @Deprecated
     public interface OnBreadCrumbClickListener {
         /**
          * Called when a bread crumb is clicked.
diff --git a/android/app/FragmentContainer.java b/android/app/FragmentContainer.java
index f8836bc..a1dd32f 100644
--- a/android/app/FragmentContainer.java
+++ b/android/app/FragmentContainer.java
@@ -24,7 +24,10 @@
 
 /**
  * Callbacks to a {@link Fragment}'s container.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentContainer}
  */
+@Deprecated
 public abstract class FragmentContainer {
     /**
      * Return the view with the given resource ID. May return {@code null} if the
diff --git a/android/app/FragmentController.java b/android/app/FragmentController.java
index cff94d8..cbb58d4 100644
--- a/android/app/FragmentController.java
+++ b/android/app/FragmentController.java
@@ -37,7 +37,10 @@
  * <p>
  * It is the responsibility of the host to take care of the Fragment's lifecycle.
  * The methods provided by {@link FragmentController} are for that purpose.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentController}
  */
+@Deprecated
 public class FragmentController {
     private final FragmentHostCallback<?> mHost;
 
diff --git a/android/app/FragmentHostCallback.java b/android/app/FragmentHostCallback.java
index 5ef23e6..1edc68e 100644
--- a/android/app/FragmentHostCallback.java
+++ b/android/app/FragmentHostCallback.java
@@ -37,7 +37,10 @@
  * Fragments may be hosted by any object; such as an {@link Activity}. In order to
  * host fragments, implement {@link FragmentHostCallback}, overriding the methods
  * applicable to the host.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
  */
+@Deprecated
 public abstract class FragmentHostCallback<E> extends FragmentContainer {
     private final Activity mActivity;
     final Context mContext;
diff --git a/android/app/FragmentManager.java b/android/app/FragmentManager.java
index 0d5cd02..12e60b8 100644
--- a/android/app/FragmentManager.java
+++ b/android/app/FragmentManager.java
@@ -74,7 +74,10 @@
  * {@link android.support.v4.app.FragmentActivity}.  See the blog post
  * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
  * Fragments For All</a> for more details.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager}
  */
+@Deprecated
 public abstract class FragmentManager {
     /**
      * Representation of an entry on the fragment back stack, as created
@@ -86,7 +89,10 @@
      * <p>Note that you should never hold on to a BackStackEntry object;
      * the identifier as returned by {@link #getId} is the only thing that
      * will be persisted across activity instances.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
      */
+    @Deprecated
     public interface BackStackEntry {
         /**
          * Return the unique identifier for the entry.  This is the only
@@ -129,7 +135,10 @@
 
     /**
      * Interface to watch for changes to the back stack.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
      */
+    @Deprecated
     public interface OnBackStackChangedListener {
         /**
          * Called whenever the contents of the back stack change.
@@ -428,7 +437,10 @@
     /**
      * Callback interface for listening to fragment state changes that happen
      * within a given FragmentManager.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
      */
+    @Deprecated
     public abstract static class FragmentLifecycleCallbacks {
         /**
          * Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
diff --git a/android/app/FragmentManagerNonConfig.java b/android/app/FragmentManagerNonConfig.java
index 50d3797..beb1a15 100644
--- a/android/app/FragmentManagerNonConfig.java
+++ b/android/app/FragmentManagerNonConfig.java
@@ -27,7 +27,10 @@
  * and passed to the state save and restore process for fragments in
  * {@link FragmentController#retainNonConfig()} and
  * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
  */
+@Deprecated
 public class FragmentManagerNonConfig {
     private final List<Fragment> mFragments;
     private final List<FragmentManagerNonConfig> mChildNonConfigs;
diff --git a/android/app/FragmentTransaction.java b/android/app/FragmentTransaction.java
index c910e90..0f4a7fb 100644
--- a/android/app/FragmentTransaction.java
+++ b/android/app/FragmentTransaction.java
@@ -21,7 +21,10 @@
  * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer
  * guide.</p>
  * </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
  */
+@Deprecated
 public abstract class FragmentTransaction {
     /**
      * Calls {@link #add(int, Fragment, String)} with a 0 containerViewId.
diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java
index e260967..d49e11f 100644
--- a/android/app/Instrumentation.java
+++ b/android/app/Instrumentation.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -418,22 +419,51 @@
      * different process.  In addition, if the given Intent resolves to
      * multiple activities, instead of displaying a dialog for the user to
      * select an activity, an exception will be thrown.
-     * 
+     *
      * <p>The function returns as soon as the activity goes idle following the
      * call to its {@link Activity#onCreate}.  Generally this means it has gone
      * through the full initialization including {@link Activity#onResume} and
      * drawn and displayed its initial window.
-     * 
+     *
      * @param intent Description of the activity to start.
-     * 
+     *
      * @see Context#startActivity
+     * @see #startActivitySync(Intent, Bundle)
      */
     public Activity startActivitySync(Intent intent) {
+        return startActivitySync(intent, null /* options */);
+    }
+
+    /**
+     * Start a new activity and wait for it to begin running before returning.
+     * In addition to being synchronous, this method as some semantic
+     * differences from the standard {@link Context#startActivity} call: the
+     * activity component is resolved before talking with the activity manager
+     * (its class name is specified in the Intent that this method ultimately
+     * starts), and it does not allow you to start activities that run in a
+     * different process.  In addition, if the given Intent resolves to
+     * multiple activities, instead of displaying a dialog for the user to
+     * select an activity, an exception will be thrown.
+     *
+     * <p>The function returns as soon as the activity goes idle following the
+     * call to its {@link Activity#onCreate}.  Generally this means it has gone
+     * through the full initialization including {@link Activity#onResume} and
+     * drawn and displayed its initial window.
+     *
+     * @param intent Description of the activity to start.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.  See {@link android.app.ActivityOptions}
+     * for how to build the Bundle supplied here; there are no supported definitions
+     * for building it manually.
+     *
+     * @see Context#startActivity(Intent, Bundle)
+     */
+    public Activity startActivitySync(Intent intent, @Nullable Bundle options) {
         validateNotAppThread();
 
         synchronized (mSync) {
             intent = new Intent(intent);
-    
+
             ActivityInfo ai = intent.resolveActivityInfo(
                 getTargetContext().getPackageManager(), 0);
             if (ai == null) {
@@ -447,7 +477,7 @@
                         + myProc + " resolved to different process "
                         + ai.processName + ": " + intent);
             }
-    
+
             intent.setComponent(new ComponentName(
                     ai.applicationInfo.packageName, ai.name));
             final ActivityWaiter aw = new ActivityWaiter(intent);
@@ -457,7 +487,7 @@
             }
             mWaitingActivities.add(aw);
 
-            getTargetContext().startActivity(intent);
+            getTargetContext().startActivity(intent, options);
 
             do {
                 try {
@@ -465,7 +495,7 @@
                 } catch (InterruptedException e) {
                 }
             } while (mWaitingActivities.contains(aw));
-         
+
             return aw.activity;
         }
     }
diff --git a/android/app/ListFragment.java b/android/app/ListFragment.java
index 0b96d84..90b77b3 100644
--- a/android/app/ListFragment.java
+++ b/android/app/ListFragment.java
@@ -144,7 +144,10 @@
  *
  * @see #setListAdapter
  * @see android.widget.ListView
+ *
+ * @deprecated Use {@link android.support.v4.app.ListFragment}
  */
+@Deprecated
 public class ListFragment extends Fragment {
     final private Handler mHandler = new Handler();
 
diff --git a/android/app/LoaderManager.java b/android/app/LoaderManager.java
index 56dfc58..7969684 100644
--- a/android/app/LoaderManager.java
+++ b/android/app/LoaderManager.java
@@ -54,11 +54,17 @@
  * <p>For more information about using loaders, read the
  * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
  * </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.LoaderManager}
  */
+@Deprecated
 public abstract class LoaderManager {
     /**
      * Callback interface for a client to interact with the manager.
+     *
+     * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
      */
+    @Deprecated
     public interface LoaderCallbacks<D> {
         /**
          * Instantiate and return a new Loader for the given ID.
diff --git a/android/app/Notification.java b/android/app/Notification.java
index 8226e0f..d5d95fb 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -22,6 +22,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@
             final Bundle ex = mN.extras;
             updateBackgroundColor(contentView);
             bindNotificationHeader(contentView, p.ambient);
-            bindLargeIcon(contentView);
+            bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
             boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
             if (p.title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@
             }
         }
 
-        private void bindLargeIcon(RemoteViews contentView) {
+        private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+                boolean alwaysShowReply) {
             if (mN.mLargeIcon == null && mN.largeIcon != null) {
                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
             }
-            if (mN.mLargeIcon != null) {
+            boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+            if (showLargeIcon) {
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
                 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
                 processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@
                 contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
                 contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
                 contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
-                // Bind the reply action
-                Action action = findReplyAction();
-                contentView.setViewVisibility(R.id.reply_icon_action, action != null
-                        ? View.VISIBLE
-                        : View.GONE);
+            }
+            // Bind the reply action
+            Action action = findReplyAction();
 
-                if (action != null) {
-                    int contrastColor = resolveContrastColor();
+            boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+            int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+            if (actionVisible) {
+                // We're only showing the icon as big if we're hiding the large icon
+                int contrastColor = resolveContrastColor();
+                int iconColor;
+                if (showLargeIcon) {
                     contentView.setDrawableTint(R.id.reply_icon_action,
                             true /* targetBackground */,
                             contrastColor, PorterDuff.Mode.SRC_ATOP);
-                    int iconColor = NotificationColorUtil.isColorLight(contrastColor)
-                            ? Color.BLACK : Color.WHITE;
-                    contentView.setDrawableTint(R.id.reply_icon_action,
-                            false /* targetBackground */,
-                            iconColor, PorterDuff.Mode.SRC_ATOP);
                     contentView.setOnClickPendingIntent(R.id.right_icon,
                             action.actionIntent);
-                    contentView.setOnClickPendingIntent(R.id.reply_icon_action,
-                            action.actionIntent);
                     contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
-                    contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+                    iconColor = NotificationColorUtil.isColorLight(contrastColor)
+                            ? Color.BLACK : Color.WHITE;
+                } else {
+                    contentView.setImageViewResource(R.id.right_icon,
+                            R.drawable.ic_reply_notification_large);
+                    contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+                    iconColor = contrastColor;
                 }
+                contentView.setDrawableTint(replyId,
+                        false /* targetBackground */,
+                        iconColor,
+                        PorterDuff.Mode.SRC_ATOP);
+                contentView.setOnClickPendingIntent(replyId,
+                        action.actionIntent);
+                contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+            } else {
+                contentView.setRemoteInputs(R.id.right_icon, null);
             }
-            contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+            contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+                    ? View.VISIBLE
+                    : View.GONE);
+            contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
                     ? View.VISIBLE
                     : View.GONE);
         }
@@ -6055,18 +6071,12 @@
         protected void restoreFromExtras(Bundle extras) {
             super.restoreFromExtras(extras);
 
-            mMessages.clear();
-            mHistoricMessages.clear();
             mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
             Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
-            if (messages != null && messages instanceof Parcelable[]) {
-                mMessages = Message.getMessagesFromBundleArray(messages);
-            }
+            mMessages = Message.getMessagesFromBundleArray(messages);
             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
-            if (histMessages != null && histMessages instanceof Parcelable[]) {
-                mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
-            }
+            mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
         }
 
         /**
@@ -6074,38 +6084,34 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            if (!increasedHeight) {
-                Message m = findLatestIncomingMessage();
-                CharSequence title = mConversationTitle != null
-                        ? mConversationTitle
-                        : (m == null) ? null : m.mSender;
-                CharSequence text = (m == null)
-                        ? null
-                        : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
-                return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
-                        mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
-            } else {
-                mBuilder.mOriginalActions = mBuilder.mActions;
-                mBuilder.mActions = new ArrayList<>();
-                RemoteViews remoteViews = makeBigContentView();
-                mBuilder.mActions = mBuilder.mOriginalActions;
-                mBuilder.mOriginalActions = null;
-                return remoteViews;
-            }
+            mBuilder.mOriginalActions = mBuilder.mActions;
+            mBuilder.mActions = new ArrayList<>();
+            RemoteViews remoteViews = makeBigContentView();
+            mBuilder.mActions = mBuilder.mOriginalActions;
+            mBuilder.mOriginalActions = null;
+            return remoteViews;
         }
 
         private Message findLatestIncomingMessage() {
-            for (int i = mMessages.size() - 1; i >= 0; i--) {
-                Message m = mMessages.get(i);
+            return findLatestIncomingMessage(mMessages);
+        }
+
+        /**
+         * @hide
+         */
+        @Nullable
+        public static Message findLatestIncomingMessage(
+                List<Message> messages) {
+            for (int i = messages.size() - 1; i >= 0; i--) {
+                Message m = messages.get(i);
                 // Incoming messages have a non-empty sender.
                 if (!TextUtils.isEmpty(m.mSender)) {
                     return m;
                 }
             }
-            if (!mMessages.isEmpty()) {
+            if (!messages.isEmpty()) {
                 // No incoming messages, fall back to outgoing message
-                return mMessages.get(mMessages.size() - 1);
+                return messages.get(messages.size() - 1);
             }
             return null;
         }
@@ -6115,118 +6121,82 @@
          */
         @Override
         public RemoteViews makeBigContentView() {
-            CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+            CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                     ? super.mBigContentTitle
                     : mConversationTitle;
-            boolean hasTitle = !TextUtils.isEmpty(title);
-
-            if (mMessages.size() == 1) {
-                // Special case for a single message: Use the big text style
-                // so the collapsed and expanded versions match nicely.
-                CharSequence bigTitle;
-                CharSequence text;
-                if (hasTitle) {
-                    bigTitle = title;
-                    text = makeMessageLine(mMessages.get(0), mBuilder);
-                } else {
-                    bigTitle = mMessages.get(0).mSender;
-                    text = mMessages.get(0).mText;
-                }
-                RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
-                        mBuilder.getBigTextLayoutResource(),
-                        mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
-                BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
-                return contentView;
+            boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+            if (isOneToOne) {
+                // Let's add the conversationTitle in case we didn't have one before and all
+                // messages are from the same sender
+                conversationTitle = createConversationTitleFromMessages();
+            } else if (hasOnlyWhiteSpaceSenders()) {
+                isOneToOne = true;
             }
-
+            boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getMessagingLayoutResource(),
-                    mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
-            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
-                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
-            // Make sure all rows are gone in case we reuse a view.
-            for (int rowId : rowIds) {
-                contentView.setViewVisibility(rowId, View.GONE);
-            }
-
-            int i=0;
-            contentView.setViewLayoutMarginBottomDimen(R.id.line1,
-                    hasTitle ? R.dimen.notification_messaging_spacing : 0);
-            contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
-                    !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
-            int contractedChildId = View.NO_ID;
-            Message contractedMessage = findLatestIncomingMessage();
-            int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
-                    - (rowIds.length - mMessages.size()));
-            while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
-                Message m = mHistoricMessages.get(firstHistoricMessage + i);
-                int rowId = rowIds[i];
-
-                contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
-                if (contractedMessage == m) {
-                    contractedChildId = rowId;
-                }
-
-                i++;
-            }
-
-            int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
-            while (firstMessage + i < mMessages.size() && i < rowIds.length) {
-                Message m = mMessages.get(firstMessage + i);
-                int rowId = rowIds[i];
-
-                contentView.setViewVisibility(rowId, View.VISIBLE);
-                contentView.setTextViewText(rowId, mBuilder.processTextSpans(
-                        makeMessageLine(m, mBuilder)));
-                mBuilder.setTextViewColorSecondary(contentView, rowId);
-
-                if (contractedMessage == m) {
-                    contractedChildId = rowId;
-                }
-
-                i++;
-            }
-            // Clear the remaining views for reapply. Ensures that historic message views can
-            // reliably be identified as being GONE and having non-null text.
-            while (i < rowIds.length) {
-                int rowId = rowIds[i];
-                contentView.setTextViewText(rowId, null);
-                i++;
-            }
-
-            // Record this here to allow transformation between the contracted and expanded views.
-            contentView.setInt(R.id.notification_messaging, "setContractedChildId",
-                    contractedChildId);
+                    mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+                            .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+            addExtras(mBuilder.mN.extras);
+            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+                    mBuilder.resolveContrastColor());
+            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+                    mBuilder.mN.mLargeIcon);
+            contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+                    isOneToOne);
+            contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+                    mBuilder.mN.extras);
             return contentView;
         }
 
-        private CharSequence makeMessageLine(Message m, Builder builder) {
-            BidiFormatter bidi = BidiFormatter.getInstance();
-            SpannableStringBuilder sb = new SpannableStringBuilder();
-            boolean colorize = builder.isColorized();
-            TextAppearanceSpan colorSpan;
-            CharSequence messageName;
-            if (TextUtils.isEmpty(m.mSender)) {
-                CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
-                sb.append(bidi.unicodeWrap(replyName),
-                        makeFontColorSpan(colorize
-                                ? builder.getPrimaryTextColor()
-                                : mBuilder.resolveContrastColor()),
-                        0 /* flags */);
-            } else {
-                sb.append(bidi.unicodeWrap(m.mSender),
-                        makeFontColorSpan(colorize
-                                ? builder.getPrimaryTextColor()
-                                : Color.BLACK),
-                        0 /* flags */);
+        private boolean hasOnlyWhiteSpaceSenders() {
+            for (int i = 0; i < mMessages.size(); i++) {
+                Message m = mMessages.get(i);
+                CharSequence sender = m.getSender();
+                if (!isWhiteSpace(sender)) {
+                    return false;
+                }
             }
-            CharSequence text = m.mText == null ? "" : m.mText;
-            sb.append("  ").append(bidi.unicodeWrap(text));
-            return sb;
+            return true;
+        }
+
+        private boolean isWhiteSpace(CharSequence sender) {
+            if (TextUtils.isEmpty(sender)) {
+                return true;
+            }
+            if (sender.toString().matches("^\\s*$")) {
+                return true;
+            }
+            // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
+            // For the presentation that we had.
+            for (int i = 0; i < sender.length(); i++) {
+                char c = sender.charAt(i);
+                if (c != '\u200B') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private CharSequence createConversationTitleFromMessages() {
+            ArraySet<CharSequence> names = new ArraySet<>();
+            for (int i = 0; i < mMessages.size(); i++) {
+                Message m = mMessages.get(i);
+                CharSequence sender = m.getSender();
+                if (sender != null) {
+                    names.add(sender);
+                }
+            }
+            SpannableStringBuilder title = new SpannableStringBuilder();
+            int size = names.size();
+            for (int i = 0; i < size; i++) {
+                CharSequence name = names.valueAt(i);
+                if (!TextUtils.isEmpty(title)) {
+                    title.append(", ");
+                }
+                title.append(BidiFormatter.getInstance().unicodeWrap(name));
+            }
+            return title;
         }
 
         /**
@@ -6234,19 +6204,9 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            if (increasedHeight) {
-                return makeBigContentView();
-            }
-            Message m = findLatestIncomingMessage();
-            CharSequence title = mConversationTitle != null
-                    ? mConversationTitle
-                    : (m == null) ? null : m.mSender;
-            CharSequence text = (m == null)
-                    ? null
-                    : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
-            return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
-                    mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+            RemoteViews remoteViews = makeBigContentView();
+            remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
+            return remoteViews;
         }
 
         private static TextAppearanceSpan makeFontColorSpan(int color) {
@@ -6394,7 +6354,15 @@
                 return bundles;
             }
 
-            static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+            /**
+             * @return A list of messages read from the bundles.
+             *
+             * @hide
+             */
+            public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+                if (bundles == null) {
+                    return new ArrayList<>();
+                }
                 List<Message> messages = new ArrayList<>(bundles.length);
                 for (int i = 0; i < bundles.length; i++) {
                     if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8455,8 @@
         boolean ambient = false;
         CharSequence title;
         CharSequence text;
+        boolean hideLargeIcon;
+        public boolean alwaysShowReply;
 
         final StandardTemplateParams reset() {
             hasProgress = true;
@@ -8511,6 +8481,16 @@
             return this;
         }
 
+        final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+            this.alwaysShowReply = alwaysShowReply;
+            return this;
+        }
+
+        final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+            this.hideLargeIcon = hideLargeIcon;
+            return this;
+        }
+
         final StandardTemplateParams ambient(boolean ambient) {
             Preconditions.checkState(title == null && text == null, "must set ambient before text");
             this.ambient = ambient;
@@ -8527,7 +8507,6 @@
                 text = extras.getCharSequence(EXTRA_TEXT);
             }
             this.text = b.processLegacyText(text, ambient);
-
             return this;
         }
     }
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index a52dc1e..f931589 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -758,10 +758,10 @@
     }
 
     /**
-     * Checks the ability to read/modify notification do not disturb policy for the calling package.
+     * Checks the ability to modify notification do not disturb policy for the calling package.
      *
      * <p>
-     * Returns true if the calling package can read/modify notification policy.
+     * Returns true if the calling package can modify notification policy.
      *
      * <p>
      * Apps can request policy access by sending the user to the activity that matches the system
@@ -839,8 +839,6 @@
      * Gets the current notification policy.
      *
      * <p>
-     * Only available if policy access is granted to this package.
-     * See {@link #isNotificationPolicyAccessGranted}.
      */
     public Policy getNotificationPolicy() {
         INotificationManager service = getService();
diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java
index 50f1f36..e48946f 100644
--- a/android/app/SystemServiceRegistry.java
+++ b/android/app/SystemServiceRegistry.java
@@ -41,6 +41,8 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutManager;
+import android.content.pm.crossprofile.CrossProfileApps;
+import android.content.pm.crossprofile.ICrossProfileApps;
 import android.content.res.Resources;
 import android.hardware.ConsumerIrManager;
 import android.hardware.ISerialManager;
@@ -81,6 +83,7 @@
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
+import android.net.NetworkWatchlistManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
@@ -134,6 +137,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccManager;
 import android.util.Log;
+import android.util.StatsManager;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
@@ -150,6 +154,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.net.INetworkWatchlistManager;
 import com.android.internal.os.IDropBoxManagerService;
 import com.android.internal.policy.PhoneLayoutInflater;
 
@@ -304,14 +309,14 @@
             }});
 
         registerService(Context.BATTERY_SERVICE, BatteryManager.class,
-                new StaticServiceFetcher<BatteryManager>() {
+                new CachedServiceFetcher<BatteryManager>() {
             @Override
-            public BatteryManager createService() throws ServiceNotFoundException {
+            public BatteryManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                 IBatteryStats stats = IBatteryStats.Stub.asInterface(
                         ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME));
                 IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub
                         .asInterface(ServiceManager.getServiceOrThrow("batteryproperties"));
-                return new BatteryManager(stats, registrar);
+                return new BatteryManager(ctx, stats, registrar);
             }});
 
         registerService(Context.NFC_SERVICE, NfcManager.class,
@@ -448,6 +453,13 @@
                   ctx.mMainThread.getHandler().getLooper());
             }});
 
+        registerService(Context.STATS_MANAGER, StatsManager.class,
+                new StaticServiceFetcher<StatsManager>() {
+                    @Override
+                    public StatsManager createService() throws ServiceNotFoundException {
+                        return new StatsManager();
+                    }});
+
         registerService(Context.STATUS_BAR_SERVICE, StatusBarManager.class,
                 new CachedServiceFetcher<StatusBarManager>() {
             @Override
@@ -862,6 +874,17 @@
                 return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b));
             }});
 
+        registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class,
+                new CachedServiceFetcher<NetworkWatchlistManager>() {
+                    @Override
+                    public NetworkWatchlistManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b =
+                                ServiceManager.getServiceOrThrow(Context.NETWORK_WATCHLIST_SERVICE);
+                        return new NetworkWatchlistManager(ctx,
+                                INetworkWatchlistManager.Stub.asInterface(b));
+                    }});
+
         registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class,
                 new CachedServiceFetcher<SystemHealthManager>() {
             @Override
@@ -909,6 +932,18 @@
             public RulesManager createService(ContextImpl ctx) {
                 return new RulesManager(ctx.getOuterContext());
             }});
+
+        registerService(Context.CROSS_PROFILE_APPS_SERVICE, CrossProfileApps.class,
+                new CachedServiceFetcher<CrossProfileApps>() {
+                    @Override
+                    public CrossProfileApps createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.CROSS_PROFILE_APPS_SERVICE);
+                        return new CrossProfileApps(ctx.getOuterContext(),
+                                ICrossProfileApps.Stub.asInterface(b));
+                    }
+                });
     }
 
     /**
diff --git a/android/app/TimePickerDialog.java b/android/app/TimePickerDialog.java
index 0f006b6..8686944 100644
--- a/android/app/TimePickerDialog.java
+++ b/android/app/TimePickerDialog.java
@@ -152,6 +152,9 @@
             public void onClick(View view) {
                 if (mTimePicker.validateInput()) {
                     TimePickerDialog.this.onClick(TimePickerDialog.this, BUTTON_POSITIVE);
+                    // Clearing focus forces the dialog to commit any pending
+                    // changes, e.g. typed text in a NumberPicker.
+                    mTimePicker.clearFocus();
                     dismiss();
                 }
             }
diff --git a/android/app/VrManager.java b/android/app/VrManager.java
index 5c6ffa3..392387a 100644
--- a/android/app/VrManager.java
+++ b/android/app/VrManager.java
@@ -198,4 +198,20 @@
             e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the current standby status of the VR device. Standby mode is only used on standalone vr
+     * devices. Standby mode is a deep sleep state where it's appropriate to turn off vr mode.
+     *
+     * @param standby True if the device is entering standby, false if it's exiting standby.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_VR_MANAGER)
+    public void setStandbyEnabled(boolean standby) {
+        try {
+            mService.setStandbyEnabled(standby);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index de27b4f..2c1fad1 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -500,15 +500,12 @@
      * @hide
      */
     public boolean supportSplitScreenWindowingMode() {
-        return supportSplitScreenWindowingMode(mWindowingMode, mActivityType);
+        return supportSplitScreenWindowingMode(mActivityType);
     }
 
     /** @hide */
-    public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) {
-        if (activityType == ACTIVITY_TYPE_ASSISTANT) {
-            return false;
-        }
-        return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+    public static boolean supportSplitScreenWindowingMode(int activityType) {
+        return activityType != ACTIVITY_TYPE_ASSISTANT;
     }
 
     /** @hide */
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 772c6d6..f0226b7 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -3246,6 +3246,7 @@
      *             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);
@@ -3270,6 +3271,7 @@
      * @throws IllegalArgumentException if the input reason string is null or empty.
      */
     public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
+        throwIfParentInstance("wipeDataWithReason");
         Preconditions.checkNotNull(reason, "CharSequence is null");
         wipeDataInternal(flags, reason.toString());
     }
@@ -3283,7 +3285,6 @@
      * @hide
      */
     private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
-        throwIfParentInstance("wipeDataWithReason");
         if (mService != null) {
             try {
                 mService.wipeDataWithReason(flags, wipeReasonForUser);
@@ -6096,8 +6097,8 @@
 
     /**
      * Flag used by {@link #createAndManageUser} to specify that the user should be created
-     * ephemeral.
-     * @hide
+     * ephemeral. Ephemeral users will be removed after switching to another user or rebooting the
+     * device.
      */
     public static final int MAKE_USER_EPHEMERAL = 0x0002;
 
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index b640bd5..530d84b 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -16,6 +16,12 @@
 
 package android.app.job;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.util.TimeUtils.formatDuration;
 
 import android.annotation.BytesLong;
@@ -25,6 +31,8 @@
 import android.annotation.RequiresPermission;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.Uri;
 import android.os.BaseBundle;
 import android.os.Bundle;
@@ -56,6 +64,7 @@
             NETWORK_TYPE_ANY,
             NETWORK_TYPE_UNMETERED,
             NETWORK_TYPE_NOT_ROAMING,
+            NETWORK_TYPE_CELLULAR,
             NETWORK_TYPE_METERED,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -69,8 +78,21 @@
     public static final int NETWORK_TYPE_UNMETERED = 2;
     /** This job requires network connectivity that is not roaming. */
     public static final int NETWORK_TYPE_NOT_ROAMING = 3;
-    /** This job requires metered connectivity such as most cellular data networks. */
-    public static final int NETWORK_TYPE_METERED = 4;
+    /** This job requires network connectivity that is a cellular network. */
+    public static final int NETWORK_TYPE_CELLULAR = 4;
+
+    /**
+     * This job requires metered connectivity such as most cellular data
+     * networks.
+     *
+     * @deprecated Cellular networks may be unmetered, or Wi-Fi networks may be
+     *             metered, so this isn't a good way of selecting a specific
+     *             transport. Instead, use {@link #NETWORK_TYPE_CELLULAR} or
+     *             {@link android.net.NetworkRequest.Builder#addTransportType(int)}
+     *             if your job requires a specific network transport.
+     */
+    @Deprecated
+    public static final int NETWORK_TYPE_METERED = NETWORK_TYPE_CELLULAR;
 
     /** Sentinel value indicating that bytes are unknown. */
     public static final int NETWORK_BYTES_UNKNOWN = -1;
@@ -253,7 +275,7 @@
     private final long triggerContentMaxDelay;
     private final boolean hasEarlyConstraint;
     private final boolean hasLateConstraint;
-    private final int networkType;
+    private final NetworkRequest networkRequest;
     private final long networkBytes;
     private final long minLatencyMillis;
     private final long maxExecutionDelayMillis;
@@ -385,10 +407,37 @@
     }
 
     /**
-     * The kind of connectivity requirements that the job has.
+     * Return the basic description of the kind of network this job requires.
+     *
+     * @deprecated This method attempts to map {@link #getRequiredNetwork()}
+     *             into the set of simple constants, which results in a loss of
+     *             fidelity. Callers should move to using
+     *             {@link #getRequiredNetwork()} directly.
+     * @see Builder#setRequiredNetworkType(int)
      */
+    @Deprecated
     public @NetworkType int getNetworkType() {
-        return networkType;
+        if (networkRequest == null) {
+            return NETWORK_TYPE_NONE;
+        } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+            return NETWORK_TYPE_UNMETERED;
+        } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+            return NETWORK_TYPE_NOT_ROAMING;
+        } else if (networkRequest.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            return NETWORK_TYPE_CELLULAR;
+        } else {
+            return NETWORK_TYPE_ANY;
+        }
+    }
+
+    /**
+     * Return the detailed description of the kind of network this job requires,
+     * or {@code null} if no specific kind of network is required.
+     *
+     * @see Builder#setRequiredNetwork(NetworkRequest)
+     */
+    public @Nullable NetworkRequest getRequiredNetwork() {
+        return networkRequest;
     }
 
     /**
@@ -438,8 +487,7 @@
      * job does not recur periodically.
      */
     public long getIntervalMillis() {
-        final long minInterval = getMinPeriodMillis();
-        return intervalMillis >= minInterval ? intervalMillis : minInterval;
+        return intervalMillis;
     }
 
     /**
@@ -447,10 +495,7 @@
      * execute at any time in a window of flex length at the end of the period.
      */
     public long getFlexMillis() {
-        long interval = getIntervalMillis();
-        long percentClamp = 5 * interval / 100;
-        long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
-        return clampedFlex <= interval ? clampedFlex : interval;
+        return flexMillis;
     }
 
     /**
@@ -459,8 +504,7 @@
      * to 30 seconds, minimum is currently 10 seconds.
      */
     public long getInitialBackoffMillis() {
-        final long minBackoff = getMinBackoffMillis();
-        return initialBackoffMillis >= minBackoff ? initialBackoffMillis : minBackoff;
+        return initialBackoffMillis;
     }
 
     /**
@@ -538,7 +582,7 @@
         if (hasLateConstraint != j.hasLateConstraint) {
             return false;
         }
-        if (networkType != j.networkType) {
+        if (!Objects.equals(networkRequest, j.networkRequest)) {
             return false;
         }
         if (networkBytes != j.networkBytes) {
@@ -601,7 +645,9 @@
         hashCode = 31 * hashCode + Long.hashCode(triggerContentMaxDelay);
         hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
         hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
-        hashCode = 31 * hashCode + networkType;
+        if (networkRequest != null) {
+            hashCode = 31 * hashCode + networkRequest.hashCode();
+        }
         hashCode = 31 * hashCode + Long.hashCode(networkBytes);
         hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
         hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
@@ -632,7 +678,11 @@
         triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
         triggerContentUpdateDelay = in.readLong();
         triggerContentMaxDelay = in.readLong();
-        networkType = in.readInt();
+        if (in.readInt() != 0) {
+            networkRequest = NetworkRequest.CREATOR.createFromParcel(in);
+        } else {
+            networkRequest = null;
+        }
         networkBytes = in.readLong();
         minLatencyMillis = in.readLong();
         maxExecutionDelayMillis = in.readLong();
@@ -661,7 +711,7 @@
                 : null;
         triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
         triggerContentMaxDelay = b.mTriggerContentMaxDelay;
-        networkType = b.mNetworkType;
+        networkRequest = b.mNetworkRequest;
         networkBytes = b.mNetworkBytes;
         minLatencyMillis = b.mMinLatencyMillis;
         maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
@@ -699,7 +749,12 @@
         out.writeTypedArray(triggerContentUris, flags);
         out.writeLong(triggerContentUpdateDelay);
         out.writeLong(triggerContentMaxDelay);
-        out.writeInt(networkType);
+        if (networkRequest != null) {
+            out.writeInt(1);
+            networkRequest.writeToParcel(out, flags);
+        } else {
+            out.writeInt(0);
+        }
         out.writeLong(networkBytes);
         out.writeLong(minLatencyMillis);
         out.writeLong(maxExecutionDelayMillis);
@@ -833,7 +888,7 @@
         private int mFlags;
         // Requirements.
         private int mConstraintFlags;
-        private int mNetworkType;
+        private NetworkRequest mNetworkRequest;
         private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
         private ArrayList<TriggerContentUri> mTriggerContentUris;
         private long mTriggerContentUpdateDelay = -1;
@@ -934,24 +989,84 @@
         }
 
         /**
-         * Set some description of the kind of network type your job needs to
-         * have. Not calling this function means the network is not necessary,
-         * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
-         * calling this function defines network as a strict requirement for
-         * your job. If the network requested is not available your job will
-         * never run. See {@link #setOverrideDeadline(long)} to change this
-         * behaviour.
+         * Set basic description of the kind of network your job requires. If
+         * you need more precise control over network capabilities, see
+         * {@link #setRequiredNetwork(NetworkRequest)}.
+         * <p>
+         * If your job doesn't need a network connection, you don't need to call
+         * this method, as the default value is {@link #NETWORK_TYPE_NONE}.
+         * <p>
+         * Calling this method defines network as a strict requirement for your
+         * job. If the network requested is not available your job will never
+         * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+         * Calling this method will override any requirements previously defined
+         * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
+         * want to call one of these methods.
          * <p class="note">
-         * Note: When your job executes in
+         * When your job executes in
          * {@link JobService#onStartJob(JobParameters)}, be sure to use the
          * specific network returned by {@link JobParameters#getNetwork()},
          * otherwise you'll use the default network which may not meet this
          * constraint.
          *
+         * @see #setRequiredNetwork(NetworkRequest)
+         * @see JobInfo#getNetworkType()
          * @see JobParameters#getNetwork()
          */
         public Builder setRequiredNetworkType(@NetworkType int networkType) {
-            mNetworkType = networkType;
+            if (networkType == NETWORK_TYPE_NONE) {
+                return setRequiredNetwork(null);
+            } else {
+                final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+
+                // All types require validated Internet
+                builder.addCapability(NET_CAPABILITY_INTERNET);
+                builder.addCapability(NET_CAPABILITY_VALIDATED);
+                builder.removeCapability(NET_CAPABILITY_NOT_VPN);
+
+                if (networkType == NETWORK_TYPE_ANY) {
+                    // No other capabilities
+                } else if (networkType == NETWORK_TYPE_UNMETERED) {
+                    builder.addCapability(NET_CAPABILITY_NOT_METERED);
+                } else if (networkType == NETWORK_TYPE_NOT_ROAMING) {
+                    builder.addCapability(NET_CAPABILITY_NOT_ROAMING);
+                } else if (networkType == NETWORK_TYPE_CELLULAR) {
+                    builder.addTransportType(TRANSPORT_CELLULAR);
+                }
+
+                return setRequiredNetwork(builder.build());
+            }
+        }
+
+        /**
+         * Set detailed description of the kind of network your job requires.
+         * <p>
+         * If your job doesn't need a network connection, you don't need to call
+         * this method, as the default is {@code null}.
+         * <p>
+         * Calling this method defines network as a strict requirement for your
+         * job. If the network requested is not available your job will never
+         * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+         * Calling this method will override any requirements previously defined
+         * by {@link #setRequiredNetworkType(int)}; you typically only want to
+         * call one of these methods.
+         * <p class="note">
+         * When your job executes in
+         * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+         * specific network returned by {@link JobParameters#getNetwork()},
+         * otherwise you'll use the default network which may not meet this
+         * constraint.
+         *
+         * @param networkRequest The detailed description of the kind of network
+         *            this job requires, or {@code null} if no specific kind of
+         *            network is required. Defining a {@link NetworkSpecifier}
+         *            is only supported for jobs that aren't persisted.
+         * @see #setRequiredNetworkType(int)
+         * @see JobInfo#getRequiredNetwork()
+         * @see JobParameters#getNetwork()
+         */
+        public Builder setRequiredNetwork(@Nullable NetworkRequest networkRequest) {
+            mNetworkRequest = networkRequest;
             return this;
         }
 
@@ -1140,6 +1255,21 @@
          *                   higher.
          */
         public Builder setPeriodic(long intervalMillis, long flexMillis) {
+            final long minPeriod = getMinPeriodMillis();
+            if (intervalMillis < minPeriod) {
+                Log.w(TAG, "Requested interval " + formatDuration(intervalMillis) + " for job "
+                        + mJobId + " is too small; raising to " + formatDuration(minPeriod));
+                intervalMillis = minPeriod;
+            }
+
+            final long percentClamp = 5 * intervalMillis / 100;
+            final long minFlex = Math.max(percentClamp, getMinFlexMillis());
+            if (flexMillis < minFlex) {
+                Log.w(TAG, "Requested flex " + formatDuration(flexMillis) + " for job " + mJobId
+                        + " is too small; raising to " + formatDuration(minFlex));
+                flexMillis = minFlex;
+            }
+
             mIsPeriodic = true;
             mIntervalMillis = intervalMillis;
             mFlexMillis = flexMillis;
@@ -1189,6 +1319,13 @@
          */
         public Builder setBackoffCriteria(long initialBackoffMillis,
                 @BackoffPolicy int backoffPolicy) {
+            final long minBackoff = getMinBackoffMillis();
+            if (initialBackoffMillis < minBackoff) {
+                Log.w(TAG, "Requested backoff " + formatDuration(initialBackoffMillis) + " for job "
+                        + mJobId + " is too small; raising to " + formatDuration(minBackoff));
+                initialBackoffMillis = minBackoff;
+            }
+
             mBackoffPolicySet = true;
             mInitialBackoffMillis = initialBackoffMillis;
             mBackoffPolicy = backoffPolicy;
@@ -1213,16 +1350,22 @@
         public JobInfo build() {
             // Allow jobs with no constraints - What am I, a database?
             if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
-                    mNetworkType == NETWORK_TYPE_NONE &&
+                    mNetworkRequest == null &&
                     mTriggerContentUris == null) {
                 throw new IllegalArgumentException("You're trying to build a job with no " +
                         "constraints, this is not allowed.");
             }
             // Check that network estimates require network type
-            if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+            if (mNetworkBytes > 0 && mNetworkRequest == null) {
                 throw new IllegalArgumentException(
                         "Can't provide estimated network usage without requiring a network");
             }
+            // We can't serialize network specifiers
+            if (mIsPersisted && mNetworkRequest != null
+                    && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) {
+                throw new IllegalArgumentException(
+                        "Network specifiers aren't supported for persistent jobs");
+            }
             // Check that a deadline was not set on a periodic job.
             if (mIsPeriodic) {
                 if (mMaxExecutionDelayMillis != 0L) {
@@ -1257,31 +1400,7 @@
                         " back-off policy, so calling setBackoffCriteria with" +
                         " setRequiresDeviceIdle is an error.");
             }
-            JobInfo job = new JobInfo(this);
-            if (job.isPeriodic()) {
-                if (job.intervalMillis != job.getIntervalMillis()) {
-                    StringBuilder builder = new StringBuilder();
-                    builder.append("Specified interval for ")
-                            .append(String.valueOf(mJobId))
-                            .append(" is ");
-                    formatDuration(mIntervalMillis, builder);
-                    builder.append(". Clamped to ");
-                    formatDuration(job.getIntervalMillis(), builder);
-                    Log.w(TAG, builder.toString());
-                }
-                if (job.flexMillis != job.getFlexMillis()) {
-                    StringBuilder builder = new StringBuilder();
-                    builder.append("Specified flex for ")
-                            .append(String.valueOf(mJobId))
-                            .append(" is ");
-                    formatDuration(mFlexMillis, builder);
-                    builder.append(". Clamped to ");
-                    formatDuration(job.getFlexMillis(), builder);
-                    Log.w(TAG, builder.toString());
-                }
-            }
-            return job;
+            return new JobInfo(this);
         }
     }
-
 }
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index f6b6b86..616a5be 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -21,8 +21,12 @@
 import android.annotation.StringDef;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
@@ -54,7 +58,12 @@
     public @interface SliceHint{ }
 
     /**
-     * Hint that this content is a title of other content in the slice.
+     * Hint that this content is a title of other content in the slice. This can also indicate that
+     * the content should be used in the shortcut representation of the slice (icon, label, action),
+     * normally this should be indicated by adding the hint on the action containing that content.
+     *
+     * @see SliceView#MODE_SHORTCUT
+     * @see SliceItem#TYPE_ACTION
      */
     public static final String HINT_TITLE       = "title";
     /**
@@ -100,6 +109,21 @@
      */
     public static final String HINT_NO_TINT     = "no_tint";
     /**
+     * Hint to indicate that this content should not be shown in the {@link SliceView#MODE_SMALL}
+     * and {@link SliceView#MODE_LARGE} modes of SliceView. This content may be used to populate
+     * the {@link SliceView#MODE_SHORTCUT} format of the slice.
+     * @hide
+     */
+    public static final String HINT_HIDDEN = "hidden";
+    /**
+     * Hint to indicate that this content has a toggle action associated with it. To indicate that
+     * the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the intent
+     * associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} which can be
+     * retrieved to see the new state of the toggle.
+     * @hide
+     */
+    public static final String HINT_TOGGLE = "toggle";
+    /**
      * Hint to indicate that this slice is incomplete and an update will be sent once
      * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
      * OS and should not be cached by apps.
@@ -112,6 +136,11 @@
      * @hide
      */
     public static final String HINT_ALT         = "alt";
+    /**
+     * Key to retrieve an extra added to an intent when a control is changed.
+     * @hide
+     */
+    public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
 
     private final SliceItem[] mItems;
     private final @SliceHint String[] mHints;
@@ -398,4 +427,58 @@
             resolver.releaseProvider(provider);
         }
     }
+
+    /**
+     * Turns a slice intent into slice content. Expects an explicit intent. If there is no
+     * {@link ContentProvider} associated with the given intent this will throw
+     * {@link IllegalArgumentException}.
+     *
+     * @param context The context to use.
+     * @param intent The intent associated with a slice.
+     * @return The Slice provided by the app or null if none is given.
+     * @see Slice
+     * @see SliceProvider#onMapIntentToUri(Intent)
+     * @see Intent
+     */
+    public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+        Preconditions.checkNotNull(intent, "intent");
+        Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
+                "Slice intent must be explicit " + intent);
+        ContentResolver resolver = context.getContentResolver();
+
+        // Check if the intent has data for the slice uri on it and use that
+        final Uri intentData = intent.getData();
+        if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
+            return bindSlice(resolver, intentData);
+        }
+        // Otherwise ask the app
+        List<ResolveInfo> providers =
+                context.getPackageManager().queryIntentContentProviders(intent, 0);
+        if (providers == null) {
+            throw new IllegalArgumentException("Unable to resolve intent " + intent);
+        }
+        String authority = providers.get(0).providerInfo.authority;
+        Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).build();
+        IContentProvider provider = resolver.acquireProvider(uri);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        try {
+            Bundle extras = new Bundle();
+            extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+            final Bundle res = provider.call(resolver.getPackageName(),
+                    SliceProvider.METHOD_MAP_INTENT, null, extras);
+            if (res == null) {
+                return null;
+            }
+            return res.getParcelable(SliceProvider.EXTRA_SLICE);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return null;
+        } finally {
+            resolver.releaseProvider(provider);
+        }
+    }
 }
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 33825b4..05f4ce6 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -16,46 +16,69 @@
 package android.app.slice;
 
 import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.app.slice.widget.SliceView;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
+import android.os.UserHandle;
 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.
+ * A SliceProvider allows an 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>
  *
- * <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>
+ * <p>
+ * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider
+ * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via
+ * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an
+ * appropriate Uri representing the slice.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ *     android:name="com.android.mypkg.MySliceProvider"
+ *     android:authorities="com.android.mypkg">
+ *     <intent-filter>
+ *         <action android:name="android.intent.action.MY_SLICE_INTENT" />
+ *     </intent-filter>
+ * </provider>}
+ * </pre>
  *
  * @see Slice
  */
 public abstract class SliceProvider extends ContentProvider {
-
     /**
      * This is the Android platform's MIME type for a slice: URI
      * containing a slice implemented through {@link SliceProvider}.
@@ -74,6 +97,14 @@
     /**
      * @hide
      */
+    public static final String METHOD_MAP_INTENT = "map_slice";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_INTENT = "slice_intent";
+    /**
+     * @hide
+     */
     public static final String EXTRA_SLICE = "slice";
 
     private static final boolean DEBUG = false;
@@ -94,6 +125,20 @@
     // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
     public abstract Slice onBindSlice(Uri sliceUri);
 
+    /**
+     * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
+     * In that case, this method can be called and is expected to return a non-null Uri representing
+     * a slice. Otherwise this will throw {@link UnsupportedOperationException}.
+     *
+     * @return Uri representing the slice associated with the provided intent.
+     * @see {@link Slice}
+     * @see {@link SliceView#setSlice(Intent)}
+     */
+    public @NonNull Uri onMapIntentToUri(Intent intent) {
+        throw new UnsupportedOperationException(
+                "This provider has not implemented intent to uri mapping");
+    }
+
     @Override
     public final int update(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
@@ -143,14 +188,31 @@
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         if (method.equals(METHOD_SLICE)) {
-            getContext().enforceCallingPermission(permission.BIND_SLICE,
-                    "Slice binding requires the permission BIND_SLICE");
             Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
+                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
+                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
+                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                        "Slice binding requires the permission BIND_SLICE");
+            }
 
             Slice s = handleBindSlice(uri);
             Bundle b = new Bundle();
             b.putParcelable(EXTRA_SLICE, s);
             return b;
+        } else if (method.equals(METHOD_MAP_INTENT)) {
+            getContext().enforceCallingPermission(permission.BIND_SLICE,
+                    "Slice binding requires the permission BIND_SLICE");
+            Intent intent = extras.getParcelable(EXTRA_INTENT);
+            Uri uri = onMapIntentToUri(intent);
+            Bundle b = new Bundle();
+            if (uri != null) {
+                Slice s = handleBindSlice(uri);
+                b.putParcelable(EXTRA_SLICE, s);
+            } else {
+                b.putParcelable(EXTRA_SLICE, null);
+            }
+            return b;
         }
         return super.call(method, arg, extras);
     }
diff --git a/android/app/slice/widget/GridView.java b/android/app/slice/widget/GridView.java
index 67a3c67..793abc0 100644
--- a/android/app/slice/widget/GridView.java
+++ b/android/app/slice/widget/GridView.java
@@ -126,6 +126,9 @@
      * Returns true if this item is just an image.
      */
     private boolean addItem(SliceItem item) {
+        if (item.hasHint(Slice.HINT_HIDDEN)) {
+            return false;
+        }
         if (item.getType() == SliceItem.TYPE_IMAGE) {
             ImageView v = new ImageView(getContext());
             v.setImageIcon(item.getIcon());
@@ -145,6 +148,9 @@
                 items.addAll(item.getSlice().getItems());
             }
             items.forEach(i -> {
+                if (i.hasHint(Slice.HINT_HIDDEN)) {
+                    return;
+                }
                 Context context = getContext();
                 switch (i.getType()) {
                     case SliceItem.TYPE_TEXT:
diff --git a/android/app/slice/widget/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java
index f45b2a8..788f6fb 100644
--- a/android/app/slice/widget/LargeTemplateView.java
+++ b/android/app/slice/widget/LargeTemplateView.java
@@ -85,9 +85,14 @@
             addList(slice, items);
         } else {
             slice.getItems().forEach(item -> {
-                if (item.hasHint(Slice.HINT_ACTIONS)) {
+                if (item.hasHint(Slice.HINT_HIDDEN)) {
+                    // If it's hidden we don't show it
+                    return;
+                } else if (item.hasHint(Slice.HINT_ACTIONS)) {
+                    // Action groups don't show in lists
                     return;
                 } else if (item.getType() == SliceItem.TYPE_COLOR) {
+                    // A color is not a list item
                     return;
                 } else if (item.getType() == SliceItem.TYPE_SLICE
                         && item.hasHint(Slice.HINT_LIST)) {
@@ -108,8 +113,12 @@
 
     private void addList(Slice slice, List<SliceItem> items) {
         List<SliceItem> sliceItems = slice.getItems();
-        sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
-        items.addAll(sliceItems);
+        sliceItems.forEach(i -> {
+            if (!i.hasHint(Slice.HINT_HIDDEN) && i.getType() != SliceItem.TYPE_COLOR) {
+                i.addHint(Slice.HINT_LIST_ITEM);
+                items.add(i);
+            }
+        });
     }
 
     /**
diff --git a/android/app/slice/widget/ShortcutView.java b/android/app/slice/widget/ShortcutView.java
index 0bca8ce..0b7ad0d 100644
--- a/android/app/slice/widget/ShortcutView.java
+++ b/android/app/slice/widget/ShortcutView.java
@@ -24,13 +24,20 @@
 import android.app.slice.widget.SliceView.SliceModeView;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.OvalShape;
 import android.net.Uri;
 
 import com.android.internal.R;
 
+import java.util.List;
+
 /**
  * @hide
  */
@@ -38,27 +45,26 @@
 
     private static final String TAG = "ShortcutView";
 
-    private PendingIntent mAction;
     private Uri mUri;
+    private PendingIntent mAction;
+    private SliceItem mLabel;
+    private SliceItem mIcon;
+
     private int mLargeIconSize;
     private int mSmallIconSize;
 
     public ShortcutView(Context context) {
         super(context);
-        mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+        final Resources res = getResources();
+        mSmallIconSize = res.getDimensionPixelSize(R.dimen.slice_icon_size);
+        mLargeIconSize = res.getDimensionPixelSize(R.dimen.slice_shortcut_size);
     }
 
     @Override
     public void setSlice(Slice slice) {
         removeAllViews();
-        SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
-        SliceItem iconItem = SliceQuery.getPrimaryIcon(slice);
-        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);
+        determineShortcutItems(getContext(), slice);
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
         if (colorItem == null) {
             colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
         }
@@ -67,13 +73,11 @@
         ShapeDrawable circle = new ShapeDrawable(new OvalShape());
         circle.setTint(color);
         setBackground(circle);
-        if (iconItem != null) {
-            final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
+        if (mIcon != null) {
+            final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE);
             final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
-            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
+            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
                     isLarge, this /* parent */);
-            mAction = sliceItem != null ? sliceItem.getAction()
-                    : null;
             mUri = slice.getUri();
             setClickable(true);
         } else {
@@ -103,4 +107,69 @@
         }
         return true;
     }
+
+    /**
+     * Looks at the slice and determines which items are best to use to compose the shortcut.
+     */
+    private void determineShortcutItems(Context context, Slice slice) {
+        List<String> h = slice.getHints();
+        SliceItem sliceItem = new SliceItem(slice, SliceItem.TYPE_SLICE,
+                h.toArray(new String[h.size()]));
+        SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION,
+                Slice.HINT_TITLE, null);
+
+        if (titleItem != null) {
+            // Preferred case: hinted action containing hinted image and text
+            mAction = titleItem.getAction();
+            mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+                    null);
+            mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+                    null);
+        } else {
+            // No hinted action; just use the first one
+            SliceItem actionItem = SliceQuery.find(sliceItem, SliceItem.TYPE_ACTION, (String) null,
+                    null);
+            mAction = (actionItem != null) ? actionItem.getAction() : null;
+        }
+        // First fallback: any hinted image and text
+        if (mIcon == null) {
+            mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+                    null);
+        }
+        // Second fallback: first image and text
+        if (mIcon == null) {
+            mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, (String) null,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, (String) null,
+                    null);
+        }
+        // Final fallback: use app info
+        if (mIcon == null || mLabel == null || mAction == null) {
+            PackageManager pm = context.getPackageManager();
+            ProviderInfo providerInfo = pm.resolveContentProvider(
+                    slice.getUri().getAuthority(), 0);
+            ApplicationInfo appInfo = providerInfo.applicationInfo;
+            if (appInfo != null) {
+                if (mIcon == null) {
+                    Drawable icon = appInfo.loadDefaultIcon(pm);
+                    mIcon = new SliceItem(SliceViewUtil.createIconFromDrawable(icon),
+                            SliceItem.TYPE_IMAGE, new String[] {Slice.HINT_LARGE});
+                }
+                if (mLabel == null) {
+                    mLabel = new SliceItem(pm.getApplicationLabel(appInfo),
+                            SliceItem.TYPE_TEXT, null);
+                }
+                if (mAction == null) {
+                    mAction = PendingIntent.getActivity(context, 0,
+                            pm.getLaunchIntentForPackage(appInfo.packageName), 0);
+                }
+            }
+        }
+    }
 }
diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java
index 5bafbc0..fa1b64c 100644
--- a/android/app/slice/widget/SliceView.java
+++ b/android/app/slice/widget/SliceView.java
@@ -115,7 +115,9 @@
      */
     public static final String MODE_LARGE       = "SLICE_LARGE";
     /**
-     * Mode indicating this slice should be presented as an icon.
+     * Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
+     * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a
+     * slice.
      */
     public static final String MODE_SHORTCUT    = "SLICE_ICON";
 
@@ -181,10 +183,25 @@
     }
 
     /**
+     * Populates this view with the {@link Slice} associated with the provided {@link Intent}. To
+     * use this method your app must have the permission
+     * {@link android.Manifest.permission#BIND_SLICE}).
+     * <p>
+     * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
+     * updated with the slice identified by the provided intent changes. The lifecycle of this
+     * observer is handled by SliceView in {@link #onAttachedToWindow()} and
+     * {@link #onDetachedFromWindow()}. To unregister this observer outside of that you can call
+     * {@link #clearSlice}.
+     *
+     * @return true if a slice was found for the provided intent.
      * @hide
      */
-    public void showSlice(Intent intent) {
-        // TODO
+    public boolean setSlice(@Nullable Intent intent) {
+        Slice s = Slice.bindSlice(mContext, intent);
+        if (s != null) {
+            return setSlice(s.getUri());
+        }
+        return s != null;
     }
 
     /**
@@ -197,8 +214,7 @@
      * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
      * To unregister this observer outside of that you can call {@link #clearSlice}.
      *
-     * @return true if the a slice was found for the provided uri.
-     * @see #clearSlice
+     * @return true if a slice was found for the provided uri.
      */
     public boolean setSlice(@NonNull Uri sliceUri) {
         Preconditions.checkNotNull(sliceUri,
@@ -210,11 +226,15 @@
         validate(sliceUri);
         Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
         if (s != null) {
+            if (mObserver != null) {
+                getContext().getContentResolver().unregisterContentObserver(mObserver);
+            }
             mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
             if (isAttachedToWindow()) {
                 registerSlice(sliceUri);
             }
-            showSlice(s);
+            mCurrentSlice = s;
+            reinflate();
         }
         return s != null;
     }
diff --git a/android/app/slice/widget/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java
index 0366998..1cf0055 100644
--- a/android/app/slice/widget/SliceViewUtil.java
+++ b/android/app/slice/widget/SliceViewUtil.java
@@ -28,6 +28,7 @@
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.view.Gravity;
@@ -141,6 +142,21 @@
     /**
      * @hide
      */
+    public static Icon createIconFromDrawable(Drawable d) {
+        if (d instanceof BitmapDrawable) {
+            return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+        }
+        Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(b);
+        d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        d.draw(canvas);
+        return Icon.createWithBitmap(b);
+    }
+
+    /**
+     * @hide
+     */
     public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
             boolean isLarge, ViewGroup parent) {
         ImageView v = new ImageView(context);
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index c827432..3a3e16e 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -261,7 +261,10 @@
 
     /**
      * @hide
+     * Changes the app standby state to the provided bucket.
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE)
     public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
         try {
             mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java
index dbaace2..29e7439 100644
--- a/android/app/usage/UsageStatsManagerInternal.java
+++ b/android/app/usage/UsageStatsManagerInternal.java
@@ -118,7 +118,15 @@
             AppIdleStateChangeListener listener);
 
     public static abstract class AppIdleStateChangeListener {
-        public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle);
+
+        /** Callback to inform listeners that the idle state has changed to a new bucket. */
+        public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
+                                                   int bucket);
+
+        /**
+         * Callback to inform listeners that the parole state has changed. This means apps are
+         * allowed to do work even if they're idle or in a low bucket.
+         */
         public abstract void onParoleStateChanged(boolean isParoleOn);
     }
 
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 106b2ef..e8895bd 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -37,6 +37,7 @@
     /**
      * Return the application.
      */
+    @SuppressWarnings("TypeParameterUnusedInFormals")
     @NonNull
     public <T extends Application> T getApplication() {
         //noinspection unchecked
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index 1ddcb1a..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.ArchTaskExecutor;
-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
-                ArchTaskExecutor.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.
-                    ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
-                }
-            }
-        }
-    };
-
-    /**
-     * Invalidates the LiveData.
-     * <p>
-     * When there are active observers, this will trigger a call to {@link #compute()}.
-     */
-    public void invalidate() {
-        ArchTaskExecutor.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/LiveData.java b/android/arch/lifecycle/LiveData.java
index 5b09c32..3aea6ac 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,410 +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.ArchTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-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 held 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())) {
-            observer.activeStateChanged(false);
-            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(@NonNull LifecycleOwner owner, @NonNull 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);
-    }
-
-    /**
-     * 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(@NonNull 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(@NonNull 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(@NonNull 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;
-        }
-        ArchTaskExecutor.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 GenericLifecycleObserver {
-        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;
-        }
-
-        @Override
-        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
-            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 (!ArchTaskExecutor.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 2b25bc9..ba76f8e 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -24,7 +24,7 @@
 import org.reactivestreams.Subscriber;
 import org.reactivestreams.Subscription;
 
-import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Adapts {@link LiveData} input and output to the ReactiveStreams spec.
@@ -53,83 +53,114 @@
     public static <T> Publisher<T> toPublisher(
             final LifecycleOwner lifecycle, final LiveData<T> liveData) {
 
-        return new Publisher<T>() {
+        return new LiveDataPublisher<>(lifecycle, liveData);
+    }
+
+    private static final class LiveDataPublisher<T> implements Publisher<T> {
+        final LifecycleOwner mLifecycle;
+        final LiveData<T> mLiveData;
+
+        LiveDataPublisher(final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+            this.mLifecycle = lifecycle;
+            this.mLiveData = liveData;
+        }
+
+        @Override
+        public void subscribe(Subscriber<? super T> subscriber) {
+            subscriber.onSubscribe(new LiveDataSubscription<T>(subscriber, mLifecycle, mLiveData));
+        }
+
+        static final class LiveDataSubscription<T> implements Subscription, Observer<T> {
+            final Subscriber<? super T> mSubscriber;
+            final LifecycleOwner mLifecycle;
+            final LiveData<T> mLiveData;
+
+            volatile boolean mCanceled;
+            // used on main thread only
             boolean mObserving;
-            boolean mCanceled;
             long mRequested;
+            // used on main thread only
             @Nullable
             T mLatest;
 
+            LiveDataSubscription(final Subscriber<? super T> subscriber,
+                    final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+                this.mSubscriber = subscriber;
+                this.mLifecycle = lifecycle;
+                this.mLiveData = liveData;
+            }
+
             @Override
-            public void subscribe(final Subscriber<? super T> subscriber) {
-                final Observer<T> observer = new Observer<T>() {
+            public void onChanged(T t) {
+                if (mCanceled) {
+                    return;
+                }
+                if (mRequested > 0) {
+                    mLatest = null;
+                    mSubscriber.onNext(t);
+                    if (mRequested != Long.MAX_VALUE) {
+                        mRequested--;
+                    }
+                } else {
+                    mLatest = t;
+                }
+            }
+
+            @Override
+            public void request(final long n) {
+                if (mCanceled) {
+                    return;
+                }
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
                     @Override
-                    public void onChanged(@Nullable T t) {
+                    public void run() {
                         if (mCanceled) {
                             return;
                         }
-                        if (mRequested > 0) {
+                        if (n <= 0L) {
+                            mCanceled = true;
+                            if (mObserving) {
+                                mLiveData.removeObserver(LiveDataSubscription.this);
+                                mObserving = false;
+                            }
                             mLatest = null;
-                            subscriber.onNext(t);
-                            if (mRequested != Long.MAX_VALUE) {
-                                mRequested--;
-                            }
-                        } else {
-                            mLatest = t;
-                        }
-                    }
-                };
-
-                subscriber.onSubscribe(new Subscription() {
-                    @Override
-                    public void request(final long n) {
-                        if (n < 0 || mCanceled) {
+                            mSubscriber.onError(
+                                    new IllegalArgumentException("Non-positive request"));
                             return;
                         }
-                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (mCanceled) {
-                                    return;
-                                }
-                                // Prevent overflowage.
-                                mRequested = mRequested + n >= mRequested
-                                        ? mRequested + n : Long.MAX_VALUE;
-                                if (!mObserving) {
-                                    mObserving = true;
-                                    liveData.observe(lifecycle, observer);
-                                } else if (mLatest != null) {
-                                    observer.onChanged(mLatest);
-                                    mLatest = null;
-                                }
-                            }
-                        });
-                    }
 
-                    @Override
-                    public void cancel() {
-                        if (mCanceled) {
-                            return;
+                        // Prevent overflowage.
+                        mRequested = mRequested + n >= mRequested
+                                ? mRequested + n : Long.MAX_VALUE;
+                        if (!mObserving) {
+                            mObserving = true;
+                            mLiveData.observe(mLifecycle, LiveDataSubscription.this);
+                        } else if (mLatest != null) {
+                            onChanged(mLatest);
+                            mLatest = null;
                         }
-                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (mCanceled) {
-                                    return;
-                                }
-                                if (mObserving) {
-                                    liveData.removeObserver(observer);
-                                    mObserving = false;
-                                }
-                                mLatest = null;
-                                mCanceled = true;
-                            }
-                        });
                     }
                 });
             }
 
-        };
+            @Override
+            public void cancel() {
+                if (mCanceled) {
+                    return;
+                }
+                mCanceled = true;
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mObserving) {
+                            mLiveData.removeObserver(LiveDataSubscription.this);
+                            mObserving = false;
+                        }
+                        mLatest = null;
+                    }
+                });
+            }
+        }
     }
 
     /**
@@ -145,6 +176,10 @@
      * 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.
+     * <p>
+     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+     * in the data that's held. In case of an error being emitted by the publisher, an error will
+     * be propagated to the main thread and the app will crash.
      *
      * @param <T> The type of data hold by this instance.
      */
@@ -166,67 +201,80 @@
      * added, it will automatically notify with the last value held in LiveData,
      * which might not be the last value emitted by the Publisher.
      *
+     * <p>
+     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+     * in the data that's held. In case of an error being emitted by the publisher, an error will
+     * be propagated to the main thread and the app will crash.
+     *
      * @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();
+        final AtomicReference<LiveDataSubscriber> mSubscriber;
 
         PublisherLiveData(@NonNull final Publisher publisher) {
             mPublisher = publisher;
+            mSubscriber = new AtomicReference<>();
         }
 
         @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;
-                    }
-                }
-            });
-
+            LiveDataSubscriber s = new LiveDataSubscriber();
+            mSubscriber.set(s);
+            mPublisher.subscribe(s);
         }
 
         @Override
         protected void onInactive() {
             super.onInactive();
-            synchronized (mLock) {
-                WeakReference<Subscription> subscriptionRef = mSubscriptionRef;
-                if (subscriptionRef != null) {
-                    Subscription subscription = subscriptionRef.get();
-                    if (subscription != null) {
-                        subscription.cancel();
+            LiveDataSubscriber s = mSubscriber.getAndSet(null);
+            if (s != null) {
+                s.cancelSubscription();
+            }
+        }
+
+        final class LiveDataSubscriber extends AtomicReference<Subscription>
+                implements Subscriber<T> {
+
+            @Override
+            public void onSubscribe(Subscription s) {
+                if (compareAndSet(null, s)) {
+                    s.request(Long.MAX_VALUE);
+                } else {
+                    s.cancel();
+                }
+            }
+
+            @Override
+            public void onNext(T item) {
+                postValue(item);
+            }
+
+            @Override
+            public void onError(final Throwable ex) {
+                mSubscriber.compareAndSet(this, null);
+
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Errors should be handled upstream, so propagate as a crash.
+                        throw new RuntimeException("LiveData does not handle errors. Errors from "
+                                + "publishers should be handled upstream and propagated as "
+                                + "state", ex);
                     }
-                    mSubscriptionRef = null;
+                });
+            }
+
+            @Override
+            public void onComplete() {
+                mSubscriber.compareAndSet(this, null);
+            }
+
+            public void cancelSubscription() {
+                Subscription s = get();
+                if (s != null) {
+                    s.cancel();
                 }
             }
         }
diff --git a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
index 7278847..83e543c 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -16,6 +16,9 @@
 
 package android.arch.lifecycle;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -34,6 +37,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import io.reactivex.Flowable;
 import io.reactivex.disposables.Disposable;
@@ -115,6 +119,41 @@
     }
 
     @Test
+    public void convertsFromPublisherSubscribeWithDelay() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        processor.delaySubscription(100, TimeUnit.SECONDS, sBackgroundScheduler);
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        liveData.removeObserver(mObserver);
+        sBackgroundScheduler.triggerActions();
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("bar");
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "bar", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherThrowsException() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        IllegalStateException exception = new IllegalStateException("test exception");
+        try {
+            processor.onError(exception);
+            fail("Runtime Exception expected");
+        } catch (RuntimeException ex) {
+            assertEquals(ex.getCause(), exception);
+        }
+    }
+
+    @Test
     public void convertsFromPublisherWithMultipleObservers() {
         final List<String> output2 = new ArrayList<>();
         PublishProcessor<String> processor = PublishProcessor.create();
@@ -125,7 +164,7 @@
         processor.onNext("foo");
         processor.onNext("bar");
 
-        // The second mObserver should only get the newest value and any later values.
+        // The second observer should only get the newest value and any later values.
         liveData.observe(mLifecycleOwner, new Observer<String>() {
             @Override
             public void onChanged(@Nullable String s) {
@@ -140,6 +179,32 @@
     }
 
     @Test
+    public void convertsFromPublisherWithMultipleObserversAfterInactive() {
+        final List<String> output2 = new ArrayList<>();
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        processor.onNext("bar");
+
+        // The second observer should only get the newest value and any later values.
+        liveData.observe(mLifecycleOwner, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                output2.add(s);
+            }
+        });
+
+        liveData.removeObserver(mObserver);
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar")));
+        assertThat(output2, is(Arrays.asList("bar", "baz")));
+    }
+
+    @Test
     public void convertsFromPublisherAfterInactive() {
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
@@ -156,7 +221,7 @@
     }
 
     @Test
-    public void convertsFromPublisherManagesSubcriptions() {
+    public void convertsFromPublisherManagesSubscriptions() {
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
 
@@ -198,7 +263,7 @@
 
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "bar", "baz"}));
+                is(new String[]{"foo", "bar", "baz"}));
     }
 
     @Test
@@ -263,10 +328,10 @@
         final Subscription subscription = subscriptionSubject.blockingSingle();
 
         subscription.request(1);
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
 
         liveData.setValue("foo");
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
 
         subscription.request(2);
         liveData.setValue("baz");
@@ -274,7 +339,7 @@
 
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "baz", "fizz"}));
+                is(new String[]{"foo", "baz", "fizz"}));
 
         // 'nyan' will be dropped as there is nothing currently requesting a stream.
         liveData.setValue("nyan");
@@ -282,13 +347,13 @@
 
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "baz", "fizz"}));
+                is(new String[]{"foo", "baz", "fizz"}));
 
         // When a new request comes in, the latest value will be pushed.
         subscription.request(1);
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "baz", "fizz", "cat"}));
+                is(new String[]{"foo", "baz", "fizz", "cat"}));
     }
 
     @Test
@@ -301,17 +366,17 @@
 
         liveData.setValue("foo");
 
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
         sBackgroundScheduler.triggerActions();
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
 
         liveData.setValue("bar");
         liveData.setValue("baz");
 
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
         sBackgroundScheduler.triggerActions();
         assertThat(mOutputProcessor.getValues(
                 new String[]{}),
-                is(new String[] {"foo", "bar", "baz"}));
+                is(new String[]{"foo", "bar", "baz"}));
     }
 }
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 647d5d7..c1dc54d 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -45,6 +45,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mockito;
 
@@ -52,11 +53,22 @@
 @RunWith(JUnit4.class)
 public class LiveDataTest {
     private PublicLiveData<String> mLiveData;
-    private LifecycleOwner mOwner;
-    private LifecycleOwner mOwner2;
-    private LifecycleRegistry mRegistry;
-    private LifecycleRegistry mRegistry2;
     private MethodExec mActiveObserversChanged;
+
+    private LifecycleOwner mOwner;
+    private LifecycleRegistry mRegistry;
+
+    private LifecycleOwner mOwner2;
+    private LifecycleRegistry mRegistry2;
+
+    private LifecycleOwner mOwner3;
+    private Lifecycle mLifecycle3;
+    private Observer<String> mObserver3;
+
+    private LifecycleOwner mOwner4;
+    private Lifecycle mLifecycle4;
+    private Observer<String> mObserver4;
+
     private boolean mInObserver;
 
     @Before
@@ -67,12 +79,10 @@
         mLiveData.activeObserversChanged = mActiveObserversChanged;
 
         mOwner = mock(LifecycleOwner.class);
-
         mRegistry = new LifecycleRegistry(mOwner);
         when(mOwner.getLifecycle()).thenReturn(mRegistry);
 
         mOwner2 = mock(LifecycleOwner.class);
-
         mRegistry2 = new LifecycleRegistry(mOwner2);
         when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
 
@@ -80,6 +90,19 @@
     }
 
     @Before
+    public void initNonLifecycleRegistry() {
+        mOwner3 = mock(LifecycleOwner.class);
+        mLifecycle3 = mock(Lifecycle.class);
+        mObserver3 = (Observer<String>) mock(Observer.class);
+        when(mOwner3.getLifecycle()).thenReturn(mLifecycle3);
+
+        mOwner4 = mock(LifecycleOwner.class);
+        mLifecycle4 = mock(Lifecycle.class);
+        mObserver4 = (Observer<String>) mock(Observer.class);
+        when(mOwner4.getLifecycle()).thenReturn(mLifecycle4);
+    }
+
+    @Before
     public void swapExecutorDelegate() {
         ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
@@ -572,100 +595,195 @@
         verify(mActiveObserversChanged, never()).onCall(anyBoolean());
     }
 
-    /**
-     * Verifies that if a lifecycle's state changes without an event, and changes to something that
-     * LiveData would become inactive in response to, LiveData will detect the change upon new data
-     * being set and become inactive.  Also verifies that once the lifecycle enters into a state
-     * that LiveData should become active to, that it does indeed become active.
-     */
     @Test
-    public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
+    public void setValue_lifecycleIsCreatedNoEvent_liveDataBecomesInactiveAndObserverNotCalled() {
 
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        // Arrange.
 
-        // Marking state as CREATED should call onInactive.
+        mLiveData.observe(mOwner3, mObserver3);
+
+        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
         reset(mActiveObserversChanged);
-        mRegistry.markState(Lifecycle.State.CREATED);
-        verify(mActiveObserversChanged).onCall(false);
-        reset(mActiveObserversChanged);
+        reset(mObserver3);
 
-        // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
-        // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
-        // and the Observer shouldn't be affected.
+        // Act.
+
         mLiveData.setValue("1");
 
-        // state is already CREATED so should not call again
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        verify(observer, never()).onChanged(anyString());
+        // Assert.
 
-        // Sanity check.  Because we've only marked the state as CREATED, sending ON_START
-        // should re-dispatch events.
-        reset(mActiveObserversChanged);
-        reset(observer);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        verify(mActiveObserversChanged).onCall(true);
-        verify(observer).onChanged("1");
+        verify(mActiveObserversChanged).onCall(false);
+        verify(mObserver3, never()).onChanged(anyString());
     }
 
-    /**
-     *  This test verifies that LiveData will detect changes in LifecycleState that would make it
-     *  inactive upon the setting of new data, but only if all of the Lifecycles it's observing
-     *  are all in those states.  It also makes sure that once it is inactive, that it will become
-     *  active again once one of the lifecycles it's observing moves to an appropriate state.
+    /*
+     * Arrange: LiveData was made inactive via SetValue (because the Lifecycle it was
+     * observing was in the CREATED state and no event was dispatched).
+     * Act: Lifecycle enters Started state and dispatches event.
+     * Assert: LiveData becomes active and dispatches new value to observer.
      */
     @Test
-    public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
+    public void test_liveDataInactiveViaSetValueThenLifecycleResumes() {
 
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
+        // Arrange.
 
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mLiveData.observe(mOwner3, mObserver3);
 
-        // Marking the state to created won't change LiveData to be inactive.
-        reset(mActiveObserversChanged);
-        mRegistry.markState(Lifecycle.State.CREATED);
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
 
-        // After setting a value, the LiveData will stay active because there is still a STARTED
-        // lifecycle being observed.  The one Observer associated with the STARTED lifecycle will
-        // also have been called, but the other Observer will not have been called.
-        reset(observer1);
-        reset(observer2);
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
         mLiveData.setValue("1");
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        verify(observer1, never()).onChanged(anyString());
-        verify(observer2).onChanged("1");
 
-        // Now we set the other Lifecycle to be inactive, live data should become inactive.
-        reset(observer1);
-        reset(observer2);
-        mRegistry2.markState(Lifecycle.State.CREATED);
-        verify(mActiveObserversChanged).onCall(false);
-        verify(observer1, never()).onChanged(anyString());
-        verify(observer2, never()).onChanged(anyString());
-
-        // Now we post another value, because both lifecycles are in the Created state, live data
-        // will not dispatch any values
         reset(mActiveObserversChanged);
-        mLiveData.setValue("2");
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        verify(observer1, never()).onChanged(anyString());
-        verify(observer2, never()).onChanged(anyString());
+        reset(mObserver3);
 
-        // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
-        // be made active and it's associated Observer will be called with the new value, but the
-        // Observer associated with the Lifecycle that is still in the Created state won't be
-        // called.
-        reset(mActiveObserversChanged);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+        // Act.
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        // Assert.
+
         verify(mActiveObserversChanged).onCall(true);
-        verify(observer1).onChanged("2");
-        verify(observer2, never()).onChanged(anyString());
+        verify(mObserver3).onChanged("1");
+    }
+
+    /*
+     * Arrange: One of two Lifecycles enter the CREATED state without sending an event.
+     * Act: Lifecycle's setValue method is called with new value.
+     * Assert: LiveData stays active and new value is dispatched to Lifecycle that is still at least
+     * STARTED.
+     */
+    @Test
+    public void setValue_oneOfTwoLifecyclesAreCreatedNoEvent() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        mLiveData.setValue("1");
+
+        // Assert.
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        verify(mObserver3, never()).onChanged(anyString());
+        verify(mObserver4).onChanged("1");
+    }
+
+    /*
+     * Arrange: Two observed Lifecycles enter the CREATED state without sending an event.
+     * Act: Lifecycle's setValue method is called with new value.
+     * Assert: LiveData becomes inactive and nothing is dispatched to either observer.
+     */
+    @Test
+    public void setValue_twoLifecyclesAreCreatedNoEvent() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        mLiveData.setValue("1");
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(false);
+        verify(mObserver3, never()).onChanged(anyString());
+        verify(mObserver3, never()).onChanged(anyString());
+    }
+
+    /*
+     * Arrange: LiveData was made inactive via SetValue (because both Lifecycles it was
+     * observing were in the CREATED state and no event was dispatched).
+     * Act: One Lifecycle enters STARTED state and dispatches lifecycle event.
+     * Assert: LiveData becomes active and dispatches new value to observer associated with started
+     * Lifecycle.
+     */
+    @Test
+    public void test_liveDataInactiveViaSetValueThenOneLifecycleResumes() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        mLiveData.setValue("1");
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(true);
+        verify(mObserver3).onChanged("1");
+        verify(mObserver4, never()).onChanged(anyString());
+    }
+
+    private GenericLifecycleObserver getGenericLifecycleObserver(Lifecycle lifecycle) {
+        ArgumentCaptor<GenericLifecycleObserver> captor =
+                ArgumentCaptor.forClass(GenericLifecycleObserver.class);
+        verify(lifecycle).addObserver(captor.capture());
+        return (captor.getValue());
     }
 
     @SuppressWarnings("WeakerAccess")
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 29cbab8..a7b3aeb 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -138,6 +138,7 @@
      */
     public static class NewInstanceFactory implements Factory {
 
+        @SuppressWarnings("ClassNewInstance")
         @NonNull
         @Override
         public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
diff --git a/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java b/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
index 6f4aa68..373b122 100644
--- a/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
+++ b/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
@@ -45,6 +45,7 @@
      *
      * @return An SQL query that can delete or update instances of T.
      */
+    @Override
     protected abstract String createQuery();
 
     /**
diff --git a/android/arch/persistence/room/Room.java b/android/arch/persistence/room/Room.java
index 2850b55..9b168fc 100644
--- a/android/arch/persistence/room/Room.java
+++ b/android/arch/persistence/room/Room.java
@@ -72,6 +72,7 @@
         return new RoomDatabase.Builder<>(context, klass, null);
     }
 
+    @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
     @NonNull
     static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
         final String fullPackage = klass.getPackage().getName();
diff --git a/android/arch/persistence/room/testing/MigrationTestHelper.java b/android/arch/persistence/room/testing/MigrationTestHelper.java
index 18e0a14..2e93bbe 100644
--- a/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ b/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -341,7 +341,7 @@
         return 0;
     }
 
-    class MigratingDelegate extends RoomOpenHelperDelegate {
+    static class MigratingDelegate extends RoomOpenHelperDelegate {
         private final boolean mVerifyDroppedTables;
 
         MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
diff --git a/android/bluetooth/BluetoothSocket.java b/android/bluetooth/BluetoothSocket.java
index 4035ee1..0569913 100644
--- a/android/bluetooth/BluetoothSocket.java
+++ b/android/bluetooth/BluetoothSocket.java
@@ -375,7 +375,7 @@
             IBluetooth bluetoothProxy =
                     BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
             if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
-            mPfd = bluetoothProxy.connectSocket(mDevice, mType,
+            mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
                     mUuid, mPort, getSecurityFlags());
             synchronized (this) {
                 if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
@@ -417,7 +417,7 @@
             return -1;
         }
         try {
-            mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
+            mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName,
                     mUuid, mPort, getSecurityFlags());
         } catch (RemoteException e) {
             Log.e(TAG, Log.getStackTraceString(new Throwable()));
diff --git a/android/content/AsyncTaskLoader.java b/android/content/AsyncTaskLoader.java
index b7545bf..6e9f09c 100644
--- a/android/content/AsyncTaskLoader.java
+++ b/android/content/AsyncTaskLoader.java
@@ -49,7 +49,10 @@
  *      fragment}
  *
  * @param <D> the data type to be loaded.
+ *
+ * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
  */
+@Deprecated
 public abstract class AsyncTaskLoader<D> extends Loader<D> {
     static final String TAG = "AsyncTaskLoader";
     static final boolean DEBUG = false;
diff --git a/android/content/ContentProviderClient.java b/android/content/ContentProviderClient.java
index f8c139f..2d490a0 100644
--- a/android/content/ContentProviderClient.java
+++ b/android/content/ContentProviderClient.java
@@ -22,6 +22,7 @@
 import android.database.CrossProcessCursorWrapper;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.DeadObjectException;
@@ -102,8 +103,16 @@
                 if (sAnrHandler == null) {
                     sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
                 }
+
+                // If the remote process hangs, we're going to kill it, so we're
+                // technically okay doing blocking calls.
+                Binder.allowBlocking(mContentProvider.asBinder());
             } else {
                 mAnrRunnable = null;
+
+                // If we're no longer watching for hangs, revert back to default
+                // blocking behavior.
+                Binder.defaultBlocking(mContentProvider.asBinder());
             }
         }
     }
@@ -511,6 +520,10 @@
     private boolean closeInternal() {
         mCloseGuard.close();
         if (mClosed.compareAndSet(false, true)) {
+            // We can't do ANR checks after we cease to exist! Reset any
+            // blocking behavior changes we might have made.
+            setDetectNotResponding(0);
+
             if (mStable) {
                 return mContentResolver.releaseProvider(mContentProvider);
             } else {
diff --git a/android/content/Context.java b/android/content/Context.java
index c165fb3..19e24ad 100644
--- a/android/content/Context.java
+++ b/android/content/Context.java
@@ -3413,6 +3413,8 @@
     public static final String NETWORK_STATS_SERVICE = "netstats";
     /** {@hide} */
     public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+    /** {@hide} */
+    public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
@@ -4042,6 +4044,13 @@
     public static final String STATS_COMPANION_SERVICE = "statscompanion";
 
     /**
+     * Use with {@link #getSystemService} to retrieve an {@link android.stats.StatsManager}.
+     * @hide
+     */
+    @SystemApi
+    public static final String STATS_MANAGER = "stats";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.content.om.OverlayManager} for managing overlay packages.
      *
@@ -4071,6 +4080,14 @@
     public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.content.pm.crossprofile.CrossProfileApps} for cross profile operations.
+     *
+     * @see #getSystemService
+     */
+    public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/android/content/CursorLoader.java b/android/content/CursorLoader.java
index c78871c..33386e5 100644
--- a/android/content/CursorLoader.java
+++ b/android/content/CursorLoader.java
@@ -38,7 +38,10 @@
  * in the desired paramters with {@link #setUri(Uri)}, {@link #setSelection(String)},
  * {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
  * and {@link #setProjection(String[])}.
+ *
+ * @deprecated Use {@link android.support.v4.content.CursorLoader}
  */
+@Deprecated
 public class CursorLoader extends AsyncTaskLoader<Cursor> {
     final ForceLoadContentObserver mObserver;
 
diff --git a/android/content/Intent.java b/android/content/Intent.java
index e47de75..bad452c 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -1728,6 +1728,9 @@
      * <p>
      * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
      * succeeded.
+     * <p>
+     * Requires {@link android.Manifest.permission#REQUEST_DELETE_PACKAGES}
+     * since {@link Build.VERSION_CODES#P}.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
@@ -3444,11 +3447,12 @@
     /**
      * A broadcast action to trigger a factory reset.
      *
-     * <p> The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission.
+     * <p>The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. The
+     * reason for the factory reset should be specified as {@link #EXTRA_REASON}.
      *
      * <p>Not for use by third-party applications.
      *
-     * @see #EXTRA_FORCE_MASTER_CLEAR
+     * @see #EXTRA_FORCE_FACTORY_RESET
      *
      * {@hide}
      */
@@ -4827,7 +4831,13 @@
     /** @hide */
     public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
 
-    /** {@hide} */
+    /**
+     * Intent extra: the reason that the operation associated with this intent is being performed.
+     *
+     * <p>Type: String
+     * @hide
+     */
+    @SystemApi
     public static final String EXTRA_REASON = "android.intent.extra.REASON";
 
     /**
diff --git a/android/content/Loader.java b/android/content/Loader.java
index 3faf13b..80f9a14 100644
--- a/android/content/Loader.java
+++ b/android/content/Loader.java
@@ -48,7 +48,10 @@
  * </div>
  *
  * @param <D> The result returned when the load is complete
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader}
  */
+@Deprecated
 public class Loader<D> {
     int mId;
     OnLoadCompleteListener<D> mListener;
@@ -66,7 +69,10 @@
      * is told it has changed.  You do not normally need to use this yourself;
      * it is used for you by {@link CursorLoader} to take care of executing
      * an update when the cursor's backing data changes.
+     *
+     * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver}
      */
+    @Deprecated
     public final class ForceLoadContentObserver extends ContentObserver {
         public ForceLoadContentObserver() {
             super(new Handler());
@@ -90,7 +96,10 @@
      * to find out when a Loader it is managing has completed so that this can
      * be reported to its client.  This interface should only be used if a
      * Loader is not being used in conjunction with LoaderManager.
+     *
+     * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener}
      */
+    @Deprecated
     public interface OnLoadCompleteListener<D> {
         /**
          * Called on the thread that created the Loader when the load is complete.
@@ -108,7 +117,10 @@
      * to find out when a Loader it is managing has been canceled so that it
      * can schedule the next Loader.  This interface should only be used if a
      * Loader is not being used in conjunction with LoaderManager.
+     *
+     * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener}
      */
+    @Deprecated
     public interface OnLoadCanceledListener<D> {
         /**
          * Called on the thread that created the Loader when the load is canceled.
diff --git a/android/content/SharedPreferences.java b/android/content/SharedPreferences.java
index 4b09fed..d3652e8 100644
--- a/android/content/SharedPreferences.java
+++ b/android/content/SharedPreferences.java
@@ -30,6 +30,11 @@
  * when they are committed to storage.  Objects that are returned from the
  * various <code>get</code> methods must be treated as immutable by the application.
  *
+ * <p>Note: This class provides strong consistency guarantees. It is using expensive operations
+ * which might slow down an app. Frequently changing properties or properties where loss can be
+ * tolerated should use other mechanisms. For more details read the comments on
+ * {@link Editor#commit()} and {@link Editor#apply()}.
+ *
  * <p><em>Note: This class does not support use across multiple processes.</em>
  *
  * <div class="special reference">
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
index 41667c4..837c00a 100644
--- a/android/content/pm/ActivityInfo.java
+++ b/android/content/pm/ActivityInfo.java
@@ -455,7 +455,6 @@
      */
     public static final int FLAG_TURN_SCREEN_ON = 0x1000000;
 
-
     /**
      * @hide Bit in {@link #flags}: If set, this component will only be seen
      * by the system user.  Only works with broadcast receivers.  Set from the
@@ -1001,20 +1000,12 @@
      * Returns true if the activity's orientation is fixed.
      * @hide
      */
-    public boolean isFixedOrientation() {
+    boolean isFixedOrientation() {
         return isFixedOrientationLandscape() || isFixedOrientationPortrait()
                 || screenOrientation == SCREEN_ORIENTATION_LOCKED;
     }
 
     /**
-     * Returns true if the specified orientation is considered fixed.
-     * @hide
-     */
-    static public boolean isFixedOrientation(int orientation) {
-        return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
-    }
-
-    /**
      * Returns true if the activity's orientation is fixed to landscape.
      * @hide
      */
diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java
index b94a410..9e54e23 100644
--- a/android/content/pm/LauncherApps.java
+++ b/android/content/pm/LauncherApps.java
@@ -282,13 +282,13 @@
         public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
 
         /**
-         * @hide include all pinned shortcuts by any launchers, not just by the caller,
+         * Include all pinned shortcuts by any launchers, not just by the caller,
          * in the result.
-         * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS}
-         * permission, this flag will be ignored.
+         *
+         * The caller must be the selected assistant app to use this flag, or have the system
+         * {@code ACCESS_SHORTCUTS} permission.
          */
-        @TestApi
-        public static final int FLAG_MATCH_ALL_PINNED = 1 << 10;
+        public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10;
 
         /**
          * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
@@ -302,7 +302,7 @@
          * @hide
          */
         public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
-                FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED;
+                FLAG_MATCH_ALL_KINDS | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
 
         /** @hide kept for unit tests */
         @Deprecated
@@ -671,9 +671,13 @@
     }
 
     /**
-     * Returns whether the caller can access the shortcut information.
+     * Returns whether the caller can access the shortcut information.  Access is currently
+     * available to:
      *
-     * <p>Only the default launcher can access the shortcut information.
+     * <ul>
+     *     <li>The current launcher (or default launcher if there is no set current launcher).</li>
+     *     <li>The currently active voice interaction service.</li>
+     * </ul>
      *
      * <p>Note when this method returns {@code false}, it may be a temporary situation because
      * the user is trying a new launcher application.  The user may decide to change the default
diff --git a/android/content/pm/PackageInstaller.java b/android/content/pm/PackageInstaller.java
index f4fdcaa..8628839 100644
--- a/android/content/pm/PackageInstaller.java
+++ b/android/content/pm/PackageInstaller.java
@@ -444,6 +444,9 @@
      * @param packageName The package to uninstall.
      * @param statusReceiver Where to deliver the result.
      */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.DELETE_PACKAGES,
+            Manifest.permission.REQUEST_DELETE_PACKAGES})
     public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
         uninstall(packageName, 0 /*flags*/, statusReceiver);
     }
@@ -476,6 +479,9 @@
      * @param versionedPackage The versioned package to uninstall.
      * @param statusReceiver Where to deliver the result.
      */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.DELETE_PACKAGES,
+            Manifest.permission.REQUEST_DELETE_PACKAGES})
     public void uninstall(@NonNull VersionedPackage versionedPackage,
             @NonNull IntentSender statusReceiver) {
         uninstall(versionedPackage, 0 /*flags*/, statusReceiver);
@@ -1184,10 +1190,10 @@
         }
 
         /**
-         * Sets the UID that initiated package installation. This is informational
+         * Sets the UID that initiated the package installation. This is informational
          * and may be used as a signal for anti-malware purposes.
          *
-         * @see PackageManager#EXTRA_VERIFICATION_INSTALLER_UID
+         * @see Intent#EXTRA_ORIGINATING_UID
          */
         public void setOriginatingUid(int originatingUid) {
             this.originatingUid = originatingUid;
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index b48829c..1c5cf15 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -102,6 +102,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -1708,13 +1709,33 @@
      */
     public static ApkLite parseApkLite(File apkFile, int flags)
             throws PackageParserException {
-        final String apkPath = apkFile.getAbsolutePath();
+        return parseApkLiteInner(apkFile, null, null, flags);
+    }
+
+    /**
+     * Utility method that retrieves lightweight details about a single APK
+     * file, including package name, split name, and install location.
+     *
+     * @param fd already open file descriptor of an apk file
+     * @param debugPathName arbitrary text name for this file, for debug output
+     * @param flags optional parse flags, such as
+     *            {@link #PARSE_COLLECT_CERTIFICATES}
+     */
+    public static ApkLite parseApkLite(FileDescriptor fd, String debugPathName, int flags)
+            throws PackageParserException {
+        return parseApkLiteInner(null, fd, debugPathName, flags);
+    }
+
+    private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName,
+            int flags) throws PackageParserException {
+        final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
 
         AssetManager assets = null;
         XmlResourceParser parser = null;
         try {
             assets = newConfiguredAssetManager();
-            int cookie = assets.addAssetPath(apkPath);
+            int cookie = fd != null
+                    ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
             if (cookie == 0) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                         "Failed to parse " + apkPath);
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
index 7fc25d8..dadfaa9 100644
--- a/android/content/pm/ShortcutServiceInternal.java
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -73,6 +73,9 @@
     public abstract boolean hasShortcutHostPermission(int launcherUserId,
             @NonNull String callingPackage, int callingPid, int callingUid);
 
+    public abstract void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+            int userId);
+
     public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
             @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
             @Nullable IntentSender resultIntent, int userId);
diff --git a/android/content/pm/crossprofile/CrossProfileApps.java b/android/content/pm/crossprofile/CrossProfileApps.java
new file mode 100644
index 0000000..c441b5f
--- /dev/null
+++ b/android/content/pm/crossprofile/CrossProfileApps.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.crossprofile;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.List;
+
+/**
+ * Class for handling cross profile operations. Apps can use this class to interact with its
+ * instance in any profile that is in {@link #getTargetUserProfiles()}. For example, app can
+ * use this class to start its main activity in managed profile.
+ */
+public class CrossProfileApps {
+    private final Context mContext;
+    private final ICrossProfileApps mService;
+
+    /** @hide */
+    public CrossProfileApps(Context context, ICrossProfileApps service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Starts the specified main activity of the caller package in the specified profile.
+     *
+     * @param component The ComponentName of the activity to launch, it must be exported and has
+     *        action {@link android.content.Intent#ACTION_MAIN}, category
+     *        {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
+     *        be thrown.
+     * @param user The UserHandle of the profile, must be one of the users returned by
+     *        {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+     *        be thrown.
+     * @param sourceBounds The Rect containing the source bounds of the clicked icon, see
+     *                     {@link android.content.Intent#setSourceBounds(Rect)}.
+     * @param startActivityOptions Options to pass to startActivity
+     */
+    public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user,
+            @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
+        try {
+            mService.startActivityAsUser(mContext.getPackageName(),
+                    component, sourceBounds, startActivityOptions, user);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return a list of user profiles that that the caller can use when calling other APIs in this
+     * class.
+     * <p>
+     * A user profile would be considered as a valid target user profile, provided that:
+     * <ul>
+     * <li>It gets caller app installed</li>
+     * <li>It is not equal to the calling user</li>
+     * <li>It is in the same profile group of calling user profile</li>
+     * <li>It is enabled</li>
+     * </ul>
+     *
+     * @see UserManager#getUserProfiles()
+     */
+    public @NonNull List<UserHandle> getTargetUserProfiles() {
+        try {
+            return mService.getTargetUserProfiles(mContext.getPackageName());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/android/content/res/AssetManager.java b/android/content/res/AssetManager.java
index f0adcd6..7866560 100644
--- a/android/content/res/AssetManager.java
+++ b/android/content/res/AssetManager.java
@@ -28,8 +28,7 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 
-import dalvik.annotation.optimization.FastNative;
-
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -694,7 +693,35 @@
 
     private native final int addAssetPathNative(String path, boolean appAsLib);
 
-     /**
+    /**
+     * Add an additional set of assets to the asset manager from an already open
+     * FileDescriptor.  Not for use by applications.
+     * This does not give full AssetManager functionality for these assets,
+     * since the origin of the file is not known for purposes of sharing,
+     * overlay resolution, and other features.  However it does allow you
+     * to do simple access to the contents of the given fd as an apk file.
+     * Performs a dup of the underlying fd, so you must take care of still closing
+     * the FileDescriptor yourself (and can do that whenever you want).
+     * Returns the cookie of the added asset, or 0 on failure.
+     * {@hide}
+     */
+    public int addAssetFd(FileDescriptor fd, String debugPathName) {
+        return addAssetFdInternal(fd, debugPathName, false);
+    }
+
+    private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
+            boolean appAsLib) {
+        synchronized (this) {
+            int res = addAssetFdNative(fd, debugPathName, appAsLib);
+            makeStringBlocks(mStringBlocks);
+            return res;
+        }
+    }
+
+    private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
+            boolean appAsLib);
+
+    /**
      * Add a set of assets to overlay an already added set of assets.
      *
      * This is only intended for application resources. System wide resources
diff --git a/android/content/res/Configuration.java b/android/content/res/Configuration.java
index dfd3bbf..26efda1 100644
--- a/android/content/res/Configuration.java
+++ b/android/content/res/Configuration.java
@@ -2105,6 +2105,7 @@
                 break;
             case DENSITY_DPI_NONE:
                 parts.add("nodpi");
+                break;
             default:
                 parts.add(config.densityDpi + "dpi");
                 break;
diff --git a/android/content/res/FontResourcesParser.java b/android/content/res/FontResourcesParser.java
index 28e9fce..6a4aae6 100644
--- a/android/content/res/FontResourcesParser.java
+++ b/android/content/res/FontResourcesParser.java
@@ -80,13 +80,15 @@
         private int mWeight;
         private int mItalic;
         private int mTtcIndex;
+        private String mVariationSettings;
         private int mResourceId;
 
         public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
-                int ttcIndex) {
+                @Nullable String variationSettings, int ttcIndex) {
             mFileName = fileName;
             mWeight = weight;
             mItalic = italic;
+            mVariationSettings = variationSettings;
             mTtcIndex = ttcIndex;
         }
 
@@ -102,6 +104,10 @@
             return mItalic;
         }
 
+        public @Nullable String getVariationSettings() {
+            return mVariationSettings;
+        }
+
         public int getTtcIndex() {
             return mTtcIndex;
         }
@@ -211,6 +217,8 @@
                 Typeface.RESOLVE_BY_FONT_TABLE);
         int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
                 Typeface.RESOLVE_BY_FONT_TABLE);
+        String variationSettings = array.getString(
+                R.styleable.FontFamilyFont_fontVariationSettings);
         int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
         String filename = array.getString(R.styleable.FontFamilyFont_font);
         array.recycle();
@@ -220,7 +228,7 @@
         if (filename == null) {
             return null;
         }
-        return new FontFileResourceEntry(filename, weight, italic, ttcIndex);
+        return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
     }
 
     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
index 386239c..3239212 100644
--- a/android/content/res/ResourcesImpl.java
+++ b/android/content/res/ResourcesImpl.java
@@ -49,6 +49,8 @@
 import android.util.Xml;
 import android.view.DisplayAdjustments;
 
+import com.android.internal.util.GrowingArrayUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -117,6 +119,13 @@
     private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
             new ConfigurationBoundResourceCache<>();
 
+    // A stack of all the resourceIds already referenced when parsing a resource. This is used to
+    // detect circular references in the xml.
+    // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
+    // calls to ResourcesImpl
+    private final ThreadLocal<LookupStack> mLookupStack =
+            ThreadLocal.withInitial(() -> new LookupStack());
+
     /** Size of the cyclical cache used to map XML files to blocks. */
     private static final int XML_BLOCK_CACHE_SIZE = 4;
 
@@ -784,19 +793,29 @@
         final Drawable dr;
 
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+        LookupStack stack = mLookupStack.get();
         try {
-            if (file.endsWith(".xml")) {
-                final XmlResourceParser rp = loadXmlResourceParser(
-                        file, id, value.assetCookie, "drawable");
-                dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
-                rp.close();
-            } else {
-                final InputStream is = mAssets.openNonAsset(
-                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
-                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
-                is.close();
+            // Perform a linear search to check if we have already referenced this resource before.
+            if (stack.contains(id)) {
+                throw new Exception("Recursive reference in drawable");
             }
-        } catch (Exception | StackOverflowError e) {
+            stack.push(id);
+            try {
+                if (file.endsWith(".xml")) {
+                    final XmlResourceParser rp = loadXmlResourceParser(
+                            file, id, value.assetCookie, "drawable");
+                    dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
+                    rp.close();
+                } else {
+                    final InputStream is = mAssets.openNonAsset(
+                            value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+                    dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
+                    is.close();
+                }
+            } finally {
+                stack.pop();
+            }
+        } catch (Exception e) {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
             final NotFoundException rnf = new NotFoundException(
                     "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
@@ -1377,4 +1396,29 @@
             }
         }
     }
+
+    private static class LookupStack {
+
+        // Pick a reasonable default size for the array, it is grown as needed.
+        private int[] mIds = new int[4];
+        private int mSize = 0;
+
+        public void push(int id) {
+            mIds = GrowingArrayUtils.append(mIds, mSize, id);
+            mSize++;
+        }
+
+        public boolean contains(int id) {
+            for (int i = 0; i < mSize; i++) {
+                if (mIds[i] == id) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void pop() {
+            mSize--;
+        }
+    }
 }
diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java
index d9c97fe..a32d528 100644
--- a/android/content/res/Resources_Delegate.java
+++ b/android/content/res/Resources_Delegate.java
@@ -887,30 +887,19 @@
 
     @LayoutlibDelegate
     static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
-        Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+        Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
 
-        if (value != null) {
-            String v = value.getSecond().getValue();
+        if (v != null) {
+            ResourceValue value = v.getSecond();
 
-            if (v != null) {
-                // check this is a file
-                File f = new File(v);
-                if (f.isFile()) {
-                    try {
-                        XmlPullParser parser = ParserFactory.create(f);
-
-                        return new BridgeXmlBlockParser(parser, getContext(resources),
-                                mPlatformResourceFlag[0]);
-                    } catch (XmlPullParserException e) {
-                        NotFoundException newE = new NotFoundException();
-                        newE.initCause(e);
-                        throw newE;
-                    } catch (FileNotFoundException e) {
-                        NotFoundException newE = new NotFoundException();
-                        newE.initCause(e);
-                        throw newE;
-                    }
-                }
+            try {
+                return ResourceHelper.getXmlBlockParser(getContext(resources), value);
+            } catch (XmlPullParserException e) {
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                        "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+                // we'll return null below.
+            } catch (FileNotFoundException e) {
+                // this shouldn't happen since we check above.
             }
         }
 
diff --git a/android/database/MergeCursor.java b/android/database/MergeCursor.java
index 2c25db7..272cfa2 100644
--- a/android/database/MergeCursor.java
+++ b/android/database/MergeCursor.java
@@ -17,7 +17,7 @@
 package android.database;
 
 /**
- * A convience class that lets you present an array of Cursors as a single linear Cursor.
+ * A convenience class that lets you present an array of Cursors as a single linear Cursor.
  * The schema of the cursors presented is entirely up to the creator of the MergeCursor, and
  * may be different if that is desired. Calls to getColumns, getColumnIndex, etc will return the
  * value for the row that the MergeCursor is currently pointing at.
diff --git a/android/database/sqlite/SQLiteConnection.java b/android/database/sqlite/SQLiteConnection.java
index c28583e..2c93a7f 100644
--- a/android/database/sqlite/SQLiteConnection.java
+++ b/android/database/sqlite/SQLiteConnection.java
@@ -289,12 +289,19 @@
 
     private void setWalModeFromConfiguration() {
         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
-            if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+            final boolean walEnabled =
+                    (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+            // Use compatibility WAL unless an app explicitly set journal/synchronous mode
+            final boolean useCompatibilityWal = mConfiguration.journalMode == null
+                    && mConfiguration.syncMode == null && mConfiguration.useCompatibilityWal;
+            if (walEnabled || useCompatibilityWal) {
                 setJournalMode("WAL");
                 setSyncMode(SQLiteGlobal.getWALSyncMode());
             } else {
-                setJournalMode(SQLiteGlobal.getDefaultJournalMode());
-                setSyncMode(SQLiteGlobal.getDefaultSyncMode());
+                setJournalMode(mConfiguration.journalMode == null
+                        ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
+                setSyncMode(mConfiguration.syncMode == null
+                        ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
             }
         }
     }
@@ -308,12 +315,10 @@
     }
 
     private static String canonicalizeSyncMode(String value) {
-        if (value.equals("0")) {
-            return "OFF";
-        } else if (value.equals("1")) {
-            return "NORMAL";
-        } else if (value.equals("2")) {
-            return "FULL";
+        switch (value) {
+            case "0": return "OFF";
+            case "1": return "NORMAL";
+            case "2": return "FULL";
         }
         return value;
     }
@@ -414,7 +419,8 @@
         boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
                 != mConfiguration.foreignKeyConstraintsEnabled;
         boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
-                & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+                & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0
+                || configuration.useCompatibilityWal != mConfiguration.useCompatibilityWal;
         boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
 
         // Update configuration parameters.
diff --git a/android/database/sqlite/SQLiteCursor.java b/android/database/sqlite/SQLiteCursor.java
index 2dc5ca4..13e6f71 100644
--- a/android/database/sqlite/SQLiteCursor.java
+++ b/android/database/sqlite/SQLiteCursor.java
@@ -22,6 +22,8 @@
 import android.os.StrictMode;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -60,6 +62,9 @@
     /** Used to find out where a cursor was allocated in case it never got released. */
     private final Throwable mStackTrace;
 
+    /** Controls fetching of rows relative to requested position **/
+    private boolean mFillWindowForwardOnly;
+
     /**
      * Execute a query and provide access to its result set through a Cursor
      * interface. For a query such as: {@code SELECT name, birth, phone FROM
@@ -136,18 +141,19 @@
 
     private void fillWindow(int requiredPos) {
         clearOrCreateWindow(getDatabase().getPath());
-
         try {
+            Preconditions.checkArgumentNonnegative(requiredPos,
+                    "requiredPos cannot be negative, but was " + requiredPos);
+
             if (mCount == NO_COUNT) {
-                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
-                mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
+                mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
                 mCursorWindowCapacity = mWindow.getNumRows();
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
                     Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
                 }
             } else {
-                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
-                        mCursorWindowCapacity);
+                int startPos = mFillWindowForwardOnly ? requiredPos : DatabaseUtils
+                        .cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
                 mQuery.fillWindow(mWindow, startPos, requiredPos, false);
             }
         } catch (RuntimeException ex) {
@@ -252,6 +258,20 @@
     }
 
     /**
+     * Controls fetching of rows relative to requested position.
+     *
+     * <p>Calling this method defines how rows will be loaded, but it doesn't affect rows that
+     * are already in the window. This setting is preserved if a new window is
+     * {@link #setWindow(CursorWindow) set}
+     *
+     * @param fillWindowForwardOnly if true, rows will be fetched starting from requested position
+     * up to the window's capacity. Default value is false.
+     */
+    public void setFillWindowForwardOnly(boolean fillWindowForwardOnly) {
+        mFillWindowForwardOnly = fillWindowForwardOnly;
+    }
+
+    /**
      * Release the native resources, if they haven't been released yet.
      */
     @Override
diff --git a/android/database/sqlite/SQLiteDatabase.java b/android/database/sqlite/SQLiteDatabase.java
index df0e262..863fb19 100644
--- a/android/database/sqlite/SQLiteDatabase.java
+++ b/android/database/sqlite/SQLiteDatabase.java
@@ -262,7 +262,8 @@
 
     private SQLiteDatabase(final String path, final int openFlags,
             CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
-            int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs) {
+            int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs,
+            String journalMode, String syncMode) {
         mCursorFactory = cursorFactory;
         mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
         mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
@@ -285,6 +286,9 @@
             }
         }
         mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
+        mConfigurationLocked.journalMode = journalMode;
+        mConfigurationLocked.syncMode = syncMode;
+        mConfigurationLocked.useCompatibilityWal = SQLiteGlobal.isCompatibilityWalSupported();
     }
 
     @Override
@@ -720,7 +724,7 @@
         SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
                 openParams.mCursorFactory, openParams.mErrorHandler,
                 openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
-                openParams.mIdleConnectionTimeout);
+                openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
         db.open();
         return db;
     }
@@ -746,7 +750,8 @@
      */
     public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
             @DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
-        SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1);
+        SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1, null,
+                null);
         db.open();
         return db;
     }
@@ -2070,15 +2075,21 @@
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
-            if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
+            final boolean oldUseCompatibilityWal = mConfigurationLocked.useCompatibilityWal;
+            final int oldFlags = mConfigurationLocked.openFlags;
+            if (!oldUseCompatibilityWal && (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
                 return;
             }
 
             mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING;
+            // If an app explicitly disables WAL, do not even use compatibility mode
+            mConfigurationLocked.useCompatibilityWal = false;
+
             try {
                 mConnectionPoolLocked.reconfigure(mConfigurationLocked);
             } catch (RuntimeException ex) {
-                mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING;
+                mConfigurationLocked.openFlags = oldFlags;
+                mConfigurationLocked.useCompatibilityWal = oldUseCompatibilityWal;
                 throw ex;
             }
         }
@@ -2295,17 +2306,21 @@
         private final DatabaseErrorHandler mErrorHandler;
         private final int mLookasideSlotSize;
         private final int mLookasideSlotCount;
-        private long mIdleConnectionTimeout;
+        private final long mIdleConnectionTimeout;
+        private final String mJournalMode;
+        private final String mSyncMode;
 
         private OpenParams(int openFlags, CursorFactory cursorFactory,
                 DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount,
-                long idleConnectionTimeout) {
+                long idleConnectionTimeout, String journalMode, String syncMode) {
             mOpenFlags = openFlags;
             mCursorFactory = cursorFactory;
             mErrorHandler = errorHandler;
             mLookasideSlotSize = lookasideSlotSize;
             mLookasideSlotCount = lookasideSlotCount;
             mIdleConnectionTimeout = idleConnectionTimeout;
+            mJournalMode = journalMode;
+            mSyncMode = syncMode;
         }
 
         /**
@@ -2372,6 +2387,28 @@
         }
 
         /**
+         * Returns <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>.
+         * This journal mode will only be used if {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING}
+         * flag is not set, otherwise a platform will use "WAL" journal mode.
+         * @see Builder#setJournalMode(String)
+         */
+        @Nullable
+        public String getJournalMode() {
+            return mJournalMode;
+        }
+
+        /**
+         * Returns <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>.
+         * This value will only be used when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag
+         * is not set, otherwise a system wide default will be used.
+         * @see Builder#setSynchronousMode(String)
+         */
+        @Nullable
+        public String getSynchronousMode() {
+            return mSyncMode;
+        }
+
+        /**
          * Creates a new instance of builder {@link Builder#Builder(OpenParams) initialized} with
          * {@code this} parameters.
          * @hide
@@ -2391,6 +2428,8 @@
             private int mOpenFlags;
             private CursorFactory mCursorFactory;
             private DatabaseErrorHandler mErrorHandler;
+            private String mJournalMode;
+            private String mSyncMode;
 
             public Builder() {
             }
@@ -2401,6 +2440,8 @@
                 mOpenFlags = params.mOpenFlags;
                 mCursorFactory = params.mCursorFactory;
                 mErrorHandler = params.mErrorHandler;
+                mJournalMode = params.mJournalMode;
+                mSyncMode = params.mSyncMode;
             }
 
             /**
@@ -2532,6 +2573,30 @@
                 return this;
             }
 
+
+            /**
+             * Sets <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>
+             * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+             */
+            @NonNull
+            public Builder setJournalMode(@NonNull  String journalMode) {
+                Preconditions.checkNotNull(journalMode);
+                mJournalMode = journalMode;
+                return this;
+            }
+
+            /**
+             * Sets <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>
+             * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+             * @return
+             */
+            @NonNull
+            public Builder setSynchronousMode(@NonNull String syncMode) {
+                Preconditions.checkNotNull(syncMode);
+                mSyncMode = syncMode;
+                return this;
+            }
+
             /**
              * Creates an instance of {@link OpenParams} with the options that were previously set
              * on this builder
@@ -2539,7 +2604,7 @@
             @NonNull
             public OpenParams build() {
                 return new OpenParams(mOpenFlags, mCursorFactory, mErrorHandler, mLookasideSlotSize,
-                        mLookasideSlotCount, mIdleConnectionTimeout);
+                        mLookasideSlotCount, mIdleConnectionTimeout, mJournalMode, mSyncMode);
             }
         }
     }
@@ -2554,4 +2619,6 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DatabaseOpenFlags {}
+
 }
+
diff --git a/android/database/sqlite/SQLiteDatabaseConfiguration.java b/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 34c9b33..a14df1e 100644
--- a/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -111,6 +111,27 @@
     public long idleConnectionTimeoutMs = Long.MAX_VALUE;
 
     /**
+     * Enables compatibility WAL mode. Applications cannot explicitly choose compatibility WAL mode,
+     * therefore it is not exposed as a flag.
+     *
+     * <p>In this mode, only database journal mode will be changed, connection pool
+     * size will still be limited to a single connection.
+     */
+    public boolean useCompatibilityWal;
+
+    /**
+     * Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
+     * <p>Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()}
+     */
+    public String journalMode;
+
+    /**
+     * Synchronous mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
+     * <p>Default is returned by {@link SQLiteGlobal#getDefaultSyncMode()}
+     */
+    public String syncMode;
+
+    /**
      * Creates a database configuration with the required parameters for opening a
      * database and default values for all other parameters.
      *
@@ -170,6 +191,9 @@
         lookasideSlotSize = other.lookasideSlotSize;
         lookasideSlotCount = other.lookasideSlotCount;
         idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
+        useCompatibilityWal = other.useCompatibilityWal;
+        journalMode = other.journalMode;
+        syncMode = other.syncMode;
     }
 
     /**
diff --git a/android/database/sqlite/SQLiteGlobal.java b/android/database/sqlite/SQLiteGlobal.java
index 94d5555..d6d9764 100644
--- a/android/database/sqlite/SQLiteGlobal.java
+++ b/android/database/sqlite/SQLiteGlobal.java
@@ -81,6 +81,16 @@
     }
 
     /**
+     * Returns true if compatibility WAL mode is supported. In this mode, only
+     * database journal mode is changed. Connection pool will use at most one connection.
+     */
+    public static boolean isCompatibilityWalSupported() {
+        return SystemProperties.getBoolean("debug.sqlite.compatibility_wal_supported",
+                Resources.getSystem().getBoolean(
+                        com.android.internal.R.bool.db_compatibility_wal_supported));
+    }
+
+    /**
      * Gets the journal size limit in bytes.
      */
     public static int getJournalSizeLimit() {
diff --git a/android/database/sqlite/SQLiteOpenHelper.java b/android/database/sqlite/SQLiteOpenHelper.java
index cc9e0f4..49f357e 100644
--- a/android/database/sqlite/SQLiteOpenHelper.java
+++ b/android/database/sqlite/SQLiteOpenHelper.java
@@ -17,6 +17,8 @@
 package android.database.sqlite;
 
 import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.database.DatabaseErrorHandler;
 import android.database.SQLException;
@@ -24,6 +26,8 @@
 import android.os.FileUtils;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.io.File;
 
 /**
@@ -69,7 +73,8 @@
      *     {@link #onUpgrade} will be used to upgrade the database; if the database is
      *     newer, {@link #onDowngrade} will be used to downgrade the database
      */
-    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
+    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+            @Nullable CursorFactory factory, int version) {
         this(context, name, factory, version, null);
     }
 
@@ -90,12 +95,33 @@
      * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
      * corruption, or null to use the default error handler.
      */
-    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
-            DatabaseErrorHandler errorHandler) {
+    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+            @Nullable CursorFactory factory, int version,
+            @Nullable DatabaseErrorHandler errorHandler) {
         this(context, name, factory, version, 0, errorHandler);
     }
 
     /**
+     * Create a helper object to create, open, and/or manage a database.
+     * This method always returns very quickly.  The database is not actually
+     * created or opened until one of {@link #getWritableDatabase} or
+     * {@link #getReadableDatabase} is called.
+     *
+     * @param context to use to open or create the database
+     * @param name of the database file, or null for an in-memory database
+     * @param version number of the database (starting at 1); if the database is older,
+     *     {@link #onUpgrade} will be used to upgrade the database; if the database is
+     *     newer, {@link #onDowngrade} will be used to downgrade the database
+     * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}.
+     *        Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be
+     *        set when the helper opens the database
+     */
+    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
+            @NonNull SQLiteDatabase.OpenParams openParams) {
+        this(context, name, version, 0, openParams.toBuilder());
+    }
+
+    /**
      * Same as {@link #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)}
      * but also accepts an integer minimumSupportedVersion as a convenience for upgrading very old
      * versions of this database that are no longer supported. If a database with older version that
@@ -118,17 +144,26 @@
      * @see #onUpgrade(SQLiteDatabase, int, int)
      * @hide
      */
-    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
-            int minimumSupportedVersion, DatabaseErrorHandler errorHandler) {
+    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+            @Nullable CursorFactory factory, int version,
+            int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler) {
+        this(context, name, version, minimumSupportedVersion,
+                new SQLiteDatabase.OpenParams.Builder());
+        mOpenParamsBuilder.setCursorFactory(factory);
+        mOpenParamsBuilder.setErrorHandler(errorHandler);
+    }
+
+    private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
+            int minimumSupportedVersion,
+            @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
+        Preconditions.checkNotNull(openParamsBuilder);
         if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
 
         mContext = context;
         mName = name;
         mNewVersion = version;
         mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
-        mOpenParamsBuilder = new SQLiteDatabase.OpenParams.Builder();
-        mOpenParamsBuilder.setCursorFactory(factory);
-        mOpenParamsBuilder.setErrorHandler(errorHandler);
+        mOpenParamsBuilder = openParamsBuilder;
         mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
     }
 
diff --git a/android/ext/services/notification/Assistant.java b/android/ext/services/notification/Assistant.java
index f535368..6fe8975 100644
--- a/android/ext/services/notification/Assistant.java
+++ b/android/ext/services/notification/Assistant.java
@@ -23,16 +23,37 @@
 import android.app.INotificationManager;
 import android.content.Context;
 import android.ext.services.R;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Environment;
+import android.os.storage.StorageManager;
 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.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
+import android.util.Xml;
 
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Map;
 
 /**
  * Notification assistant that provides guidance on notification channel blocking
@@ -41,19 +62,112 @@
     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<>();
+    private static final String TAG_ASSISTANT = "assistant";
+    private static final String TAG_IMPRESSION = "impression-set";
+    private static final String ATT_KEY = "key";
+    private static final int DB_VERSION = 1;
+    private static final String ATTR_VERSION = "version";
+
+    private static final ArrayList<Integer> PREJUDICAL_DISMISSALS = new ArrayList<>();
     static {
-        DISMISS_WITH_PREJUDICE.add(REASON_CANCEL);
-        DISMISS_WITH_PREJUDICE.add(REASON_LISTENER_CANCEL);
+        PREJUDICAL_DISMISSALS.add(REASON_CANCEL);
+        PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
     }
 
     // key : impressions tracker
-    // TODO: persist across reboots
+    // TODO: prune deleted channels and apps
     ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
     // SBN key : channel id
     ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
 
     private Ranking mFakeRanking = null;
+    private AtomicFile mFile = null;
+
+    public Assistant() {
+    }
+
+    private void loadFile() {
+        if (DEBUG) Slog.d(TAG, "loadFile");
+        AsyncTask.execute(() -> {
+            InputStream infile = null;
+            try {
+                infile = mFile.openRead();
+                readXml(infile);
+            } catch (FileNotFoundException e) {
+                // No data yet
+            } catch (IOException e) {
+                Log.e(TAG, "Unable to read channel impressions", e);
+            } catch (NumberFormatException | XmlPullParserException e) {
+                Log.e(TAG, "Unable to parse channel impressions", e);
+            } finally {
+                IoUtils.closeQuietly(infile);
+            }
+        });
+    }
+
+    protected void readXml(InputStream stream)
+            throws XmlPullParserException, NumberFormatException, IOException {
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(stream, StandardCharsets.UTF_8.name());
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (!TAG_ASSISTANT.equals(parser.getName())) {
+                continue;
+            }
+            final int impressionOuterDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, impressionOuterDepth)) {
+                if (!TAG_IMPRESSION.equals(parser.getName())) {
+                    continue;
+                }
+                String key = parser.getAttributeValue(null, ATT_KEY);
+                ChannelImpressions ci = new ChannelImpressions();
+                ci.populateFromXml(parser);
+                synchronized (mkeyToImpressions) {
+                    ci.append(mkeyToImpressions.get(key));
+                    mkeyToImpressions.put(key, ci);
+                }
+            }
+        }
+    }
+
+    private void saveFile() throws IOException {
+        AsyncTask.execute(() -> {
+            final FileOutputStream stream;
+            try {
+                stream = mFile.startWrite();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to save policy file", e);
+                return;
+            }
+            try {
+                final XmlSerializer out = new FastXmlSerializer();
+                out.setOutput(stream, StandardCharsets.UTF_8.name());
+                writeXml(out);
+                mFile.finishWrite(stream);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to save impressions file, restoring backup", e);
+                mFile.failWrite(stream);
+            }
+        });
+    }
+
+    protected void writeXml(XmlSerializer out) throws IOException {
+        out.startDocument(null, true);
+        out.startTag(null, TAG_ASSISTANT);
+        out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
+        synchronized (mkeyToImpressions) {
+            for (Map.Entry<String, ChannelImpressions> entry
+                    : mkeyToImpressions.entrySet()) {
+                // TODO: ensure channel still exists
+                out.startTag(null, TAG_IMPRESSION);
+                out.attribute(null, ATT_KEY, entry.getKey());
+                entry.getValue().writeXml(out);
+                out.endTag(null, TAG_IMPRESSION);
+            }
+        }
+        out.endTag(null, TAG_ASSISTANT);
+        out.endDocument();
+    }
 
     @Override
     public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
@@ -87,26 +201,38 @@
     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
             NotificationStats stats, int reason) {
         try {
+            boolean updatedImpressions = false;
             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();
+            synchronized (mkeyToImpressions) {
+                ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
+                        new ChannelImpressions());
+                if (stats.hasSeen()) {
+                    ci.incrementViews();
+                    updatedImpressions = true;
+                }
+                if (PREJUDICAL_DISMISSALS.contains(reason)) {
+                    if ((!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 " + key);
+                        ci.incrementDismissals();
+                        updatedImpressions = true;
+                    } else {
+                        if (DEBUG) Slog.i(TAG, "reset streak " + key);
+                        if (ci.getStreak() > 0) {
+                            updatedImpressions = true;
+                        }
+                        ci.resetStreak();
+                    }
+                }
+                mkeyToImpressions.put(key, ci);
             }
-            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();
+            if (updatedImpressions) {
+                saveFile();
             }
-            mkeyToImpressions.put(key, ci);
         } catch (Throwable e) {
             Slog.e(TAG, "Error occurred processing removal", e);
         }
@@ -121,6 +247,11 @@
     public void onListenerConnected() {
         if (DEBUG) Log.i(TAG, "CONNECTED");
         try {
+            mFile = new AtomicFile(new File(new File(
+                    Environment.getDataUserCePackageDirectory(
+                            StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
+                    "assistant"), "block_stats.xml"));
+            loadFile();
             for (StatusBarNotification sbn : getActiveNotifications()) {
                 onNotificationPosted(sbn);
             }
@@ -129,7 +260,7 @@
         }
     }
 
-    private String getKey(String pkg, int userId, String channelId) {
+    protected String getKey(String pkg, int userId, String channelId) {
         return pkg + "|" + userId + "|" + channelId;
     }
 
@@ -151,6 +282,11 @@
     }
 
     // for testing
+
+    protected void setFile(AtomicFile file) {
+        mFile = file;
+    }
+
     protected void setFakeRanking(Ranking ranking) {
         mFakeRanking = ranking;
     }
@@ -162,4 +298,16 @@
     protected void setContext(Context context) {
         mSystemContext = context;
     }
+
+    protected ChannelImpressions getImpressions(String key) {
+        synchronized (mkeyToImpressions) {
+            return mkeyToImpressions.get(key);
+        }
+    }
+
+    protected void insertImpressions(String key, ChannelImpressions ci) {
+        synchronized (mkeyToImpressions) {
+            mkeyToImpressions.put(key, ci);
+        }
+    }
 }
\ No newline at end of file
diff --git a/android/ext/services/notification/ChannelImpressions.java b/android/ext/services/notification/ChannelImpressions.java
index 30567cc..4ad4b24 100644
--- a/android/ext/services/notification/ChannelImpressions.java
+++ b/android/ext/services/notification/ChannelImpressions.java
@@ -18,14 +18,23 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
 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;
+    static final String ATT_DISMISSALS = "dismisses";
+    static final String ATT_VIEWS = "views";
+    static final String ATT_STREAK = "streak";
 
     private int mDismissals = 0;
     private int mViews = 0;
@@ -62,6 +71,14 @@
         mStreak++;
     }
 
+    public void append(ChannelImpressions additionalImpressions) {
+        if (additionalImpressions != null) {
+            mViews += additionalImpressions.getViews();
+            mStreak += additionalImpressions.getStreak();
+            mDismissals += additionalImpressions.getDismissals();
+        }
+    }
+
     public void incrementViews() {
         mViews++;
     }
@@ -134,4 +151,36 @@
         sb.append('}');
         return sb.toString();
     }
+
+    protected void populateFromXml(XmlPullParser parser) {
+        mDismissals = safeInt(parser, ATT_DISMISSALS, 0);
+        mStreak = safeInt(parser, ATT_STREAK, 0);
+        mViews = safeInt(parser, ATT_VIEWS, 0);
+    }
+
+    protected void writeXml(XmlSerializer out) throws IOException {
+        if (mDismissals != 0) {
+            out.attribute(null, ATT_DISMISSALS, String.valueOf(mDismissals));
+        }
+        if (mStreak != 0) {
+            out.attribute(null, ATT_STREAK, String.valueOf(mStreak));
+        }
+        if (mViews != 0) {
+            out.attribute(null, ATT_VIEWS, String.valueOf(mViews));
+        }
+    }
+
+    private static int safeInt(XmlPullParser parser, String att, int defValue) {
+        final String val = parser.getAttributeValue(null, att);
+        return tryParseInt(val, defValue);
+    }
+
+    private static int tryParseInt(String value, int defValue) {
+        if (TextUtils.isEmpty(value)) return defValue;
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return defValue;
+        }
+    }
 }
diff --git a/android/graphics/Paint.java b/android/graphics/Paint.java
index 1a06a56..317144a 100644
--- a/android/graphics/Paint.java
+++ b/android/graphics/Paint.java
@@ -88,7 +88,7 @@
      * A map from a string representation of the LocaleList to Minikin's language list ID.
      */
     @GuardedBy("sCacheLock")
-    private static final HashMap<String, Integer> sMinikinLangListIdCache = new HashMap<>();
+    private static final HashMap<String, Integer> sMinikinLocaleListIdCache = new HashMap<>();
 
     /**
      * @hide
@@ -1445,16 +1445,16 @@
 
     private void syncTextLocalesWithMinikin() {
         final String languageTags = mLocales.toLanguageTags();
-        final Integer minikinLangListId;
+        final Integer minikinLocaleListId;
         synchronized (sCacheLock) {
-            minikinLangListId = sMinikinLangListIdCache.get(languageTags);
-            if (minikinLangListId == null) {
+            minikinLocaleListId = sMinikinLocaleListIdCache.get(languageTags);
+            if (minikinLocaleListId == null) {
                 final int newID = nSetTextLocales(mNativePaint, languageTags);
-                sMinikinLangListIdCache.put(languageTags, newID);
+                sMinikinLocaleListIdCache.put(languageTags, newID);
                 return;
             }
         }
-        nSetTextLocalesByMinikinLangListId(mNativePaint, minikinLangListId.intValue());
+        nSetTextLocalesByMinikinLocaleListId(mNativePaint, minikinLocaleListId.intValue());
     }
 
     /**
@@ -2918,8 +2918,8 @@
     @CriticalNative
     private static native void nSetTextAlign(long paintPtr, int align);
     @CriticalNative
-    private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
-            int mMinikinLangListId);
+    private static native void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
+            int mMinikinLocaleListId);
     @CriticalNative
     private static native void nSetShadowLayer(long paintPtr,
             float radius, float dx, float dy, int color);
diff --git a/android/graphics/Paint_Delegate.java b/android/graphics/Paint_Delegate.java
index ef45203..6242702 100644
--- a/android/graphics/Paint_Delegate.java
+++ b/android/graphics/Paint_Delegate.java
@@ -950,7 +950,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+    /*package*/ static void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
             int mMinikinLangListId) {
         // FIXME
     }
diff --git a/android/graphics/Rect.java b/android/graphics/Rect.java
index 3dc928d..aff942d 100644
--- a/android/graphics/Rect.java
+++ b/android/graphics/Rect.java
@@ -475,6 +475,19 @@
     }
 
     /**
+     * If the specified rectangle intersects this rectangle, set this rectangle to that
+     * intersection, otherwise set this rectangle to the empty rectangle.
+     * @see #inset(int, int, int, int) but without checking if the rects overlap.
+     * @hide
+     */
+    public void intersectUnchecked(Rect other) {
+        left = Math.max(left, other.left);
+        top = Math.max(top, other.top);
+        right = Math.min(right, other.right);
+        bottom = Math.min(bottom, other.bottom);
+    }
+
+    /**
      * If rectangles a and b intersect, return true and set this rectangle to
      * that intersection, otherwise return false and do not change this
      * rectangle. No check is performed to see if either rectangle is empty.
diff --git a/android/graphics/Typeface.java b/android/graphics/Typeface.java
index 9961ed6..3d65bd2 100644
--- a/android/graphics/Typeface.java
+++ b/android/graphics/Typeface.java
@@ -250,10 +250,10 @@
 
         FontFamily fontFamily = new FontFamily();
         for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
-            // TODO: Add variation font support. (b/37853920)
             if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
                     0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
-                    fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
+                    fontFile.getWeight(), fontFile.getItalic(),
+                    FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
                 return null;
             }
         }
diff --git a/android/graphics/Typeface_Delegate.java b/android/graphics/Typeface_Delegate.java
index b9c0353..d793ade 100644
--- a/android/graphics/Typeface_Delegate.java
+++ b/android/graphics/Typeface_Delegate.java
@@ -16,19 +16,32 @@
 
 package android.graphics;
 
+import com.android.SdkConstants;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.RenderAction;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.res.FontResourcesParser;
 import android.graphics.FontFamily_Delegate.FontVariant;
 import android.graphics.fonts.FontVariationAxis;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 
 import java.awt.Font;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.lang.ref.SoftReference;
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
@@ -231,12 +244,72 @@
         return fontFamily;
     }
 
+    /**
+     * Loads a single font or font family from disk
+     */
+    @Nullable
+    public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path,
+            boolean isFramework) {
+        // Check if this is an asset that we've already loaded dynamically
+        Typeface typeface = Typeface.findFromCache(context.getAssets(), path);
+        if (typeface != null) {
+            return typeface;
+        }
+
+        String lowerCaseValue = path.toLowerCase();
+        if (lowerCaseValue.endsWith(SdkConstants.DOT_XML)) {
+            // create a block parser for the file
+            Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+                    RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+            XmlPullParser parser = null;
+            if (psiParserSupport != null && psiParserSupport) {
+                parser = context.getLayoutlibCallback().getXmlFileParser(path);
+            } else {
+                File f = new File(path);
+                if (f.isFile()) {
+                    try {
+                        parser = ParserFactory.create(f);
+                    } catch (XmlPullParserException | FileNotFoundException e) {
+                        // this is an error and not warning since the file existence is checked
+                        // before
+                        // attempting to parse it.
+                        Bridge.getLog().error(null, "Failed to parse file " + path, e,
+                                null /*data*/);
+                    }
+                }
+            }
+
+            if (parser != null) {
+                BridgeXmlBlockParser blockParser =
+                        new BridgeXmlBlockParser(parser, context, isFramework);
+                try {
+                    FontResourcesParser.FamilyResourceEntry entry =
+                            FontResourcesParser.parse(blockParser, context.getResources());
+                    typeface = Typeface.createFromResources(entry, context.getAssets(), path);
+                } catch (XmlPullParserException | IOException e) {
+                    Bridge.getLog().error(null, "Failed to parse file " + path, e, null /*data*/);
+                } finally {
+                    blockParser.ensurePopped();
+                }
+            } else {
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                        String.format("File %s does not exist (or is not a file)", path),
+                        null /*data*/);
+            }
+        } else {
+            typeface = Typeface.createFromResources(context.getAssets(), path, 0);
+        }
+
+        return typeface;
+    }
+
     @LayoutlibDelegate
     /*package*/ static Typeface create(String familyName, int style) {
         if (familyName != null && Files.exists(Paths.get(familyName))) {
             // Workaround for b/64137851
             // Support lib will call this method after failing to create the TypefaceCompat.
-            return Typeface.createFromFile(familyName);
+            return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName,
+                    false);
         }
         return Typeface.create_Original(familyName, style);
     }
diff --git a/android/graphics/drawable/AnimatedVectorDrawable.java b/android/graphics/drawable/AnimatedVectorDrawable.java
index 90d6ab8..e74dc6d 100644
--- a/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -132,7 +132,7 @@
  *         <td>translateY</td>
  *     </tr>
  *     <tr>
- *         <td rowspan="8">&lt;path&gt;</td>
+ *         <td rowspan="9">&lt;path&gt;</td>
  *         <td>pathData</td>
  *     </tr>
  *     <tr>
@@ -154,6 +154,9 @@
  *         <td>trimPathStart</td>
  *     </tr>
  *     <tr>
+ *         <td>trimPathEnd</td>
+ *     </tr>
+ *     <tr>
  *         <td>trimPathOffset</td>
  *     </tr>
  *     <tr>
diff --git a/android/graphics/drawable/RippleBackground.java b/android/graphics/drawable/RippleBackground.java
index 3bf4f90..dea194e 100644
--- a/android/graphics/drawable/RippleBackground.java
+++ b/android/graphics/drawable/RippleBackground.java
@@ -36,138 +36,69 @@
 
     private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
 
-    private static final int OPACITY_ENTER_DURATION = 600;
-    private static final int OPACITY_ENTER_DURATION_FAST = 120;
-    private static final int OPACITY_EXIT_DURATION = 480;
+    private static final int OPACITY_DURATION = 80;
 
-    // Hardware rendering properties.
-    private CanvasProperty<Paint> mPropPaint;
-    private CanvasProperty<Float> mPropRadius;
-    private CanvasProperty<Float> mPropX;
-    private CanvasProperty<Float> mPropY;
+    private ObjectAnimator mAnimator;
 
-    // Software rendering properties.
     private float mOpacity = 0;
 
     /** Whether this ripple is bounded. */
     private boolean mIsBounded;
 
-    public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded,
-            boolean forceSoftware) {
-        super(owner, bounds, forceSoftware);
+    private boolean mFocused = false;
+    private boolean mHovered = false;
+
+    public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded) {
+        super(owner, bounds);
 
         mIsBounded = isBounded;
     }
 
     public boolean isVisible() {
-        return mOpacity > 0 || isHardwareAnimating();
+        return mOpacity > 0;
     }
 
-    @Override
-    protected boolean drawSoftware(Canvas c, Paint p) {
-        boolean hasContent = false;
-
+    public void draw(Canvas c, Paint p) {
         final int origAlpha = p.getAlpha();
-        final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+        final int alpha = Math.min((int) (origAlpha * mOpacity + 0.5f), 255);
         if (alpha > 0) {
             p.setAlpha(alpha);
             c.drawCircle(0, 0, mTargetRadius, p);
             p.setAlpha(origAlpha);
-            hasContent = true;
         }
-
-        return hasContent;
     }
 
-    @Override
-    protected boolean drawHardware(DisplayListCanvas c) {
-        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
-        return true;
-    }
-
-    @Override
-    protected Animator createSoftwareEnter(boolean fast) {
-        // Linear enter based on current opacity.
-        final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
-        final int duration = (int) ((1 - mOpacity) * maxDuration);
-
-        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(duration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
-        return opacity;
-    }
-
-    @Override
-    protected Animator createSoftwareExit() {
-        final AnimatorSet set = new AnimatorSet();
-
-        // Linear exit after enter is completed.
-        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, OPACITY, 0);
-        exit.setInterpolator(LINEAR_INTERPOLATOR);
-        exit.setDuration(OPACITY_EXIT_DURATION);
-        exit.setAutoCancel(true);
-
-        final AnimatorSet.Builder builder = set.play(exit);
-
-        // Linear "fast" enter based on current opacity.
-        final int fastEnterDuration = mIsBounded ?
-                (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
-        if (fastEnterDuration > 0) {
-            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, OPACITY, 1);
-            enter.setInterpolator(LINEAR_INTERPOLATOR);
-            enter.setDuration(fastEnterDuration);
-            enter.setAutoCancel(true);
-
-            builder.after(enter);
+    public void setState(boolean focused, boolean hovered, boolean animateChanged) {
+        if (mHovered != hovered || mFocused != focused) {
+            mHovered = hovered;
+            mFocused = focused;
+            onStateChanged(animateChanged);
         }
-
-        return set;
     }
 
-    @Override
-    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
-        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
-
-        final int targetAlpha = p.getAlpha();
-        final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
-        p.setAlpha(currentAlpha);
-
-        mPropPaint = CanvasProperty.createPaint(p);
-        mPropRadius = CanvasProperty.createFloat(mTargetRadius);
-        mPropX = CanvasProperty.createFloat(0);
-        mPropY = CanvasProperty.createFloat(0);
-
-        final int fastEnterDuration = mIsBounded ?
-                (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
-
-        // Linear exit after enter is completed.
-        final RenderNodeAnimator exit = new RenderNodeAnimator(
-                mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-        exit.setInterpolator(LINEAR_INTERPOLATOR);
-        exit.setDuration(OPACITY_EXIT_DURATION);
-        if (fastEnterDuration > 0) {
-            exit.setStartDelay(fastEnterDuration);
-            exit.setStartValue(targetAlpha);
+    private void onStateChanged(boolean animateChanged) {
+        float newOpacity = 0.0f;
+        if (mHovered) newOpacity += 1.0f;
+        if (mFocused) newOpacity += 1.0f;
+        if (mAnimator != null) {
+            mAnimator.cancel();
+            mAnimator = null;
         }
-        set.add(exit);
-
-        // Linear "fast" enter based on current opacity.
-        if (fastEnterDuration > 0) {
-            final RenderNodeAnimator enter = new RenderNodeAnimator(
-                    mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
-            enter.setInterpolator(LINEAR_INTERPOLATOR);
-            enter.setDuration(fastEnterDuration);
-            set.add(enter);
+        if (animateChanged) {
+            mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity);
+            mAnimator.setDuration(OPACITY_DURATION);
+            mAnimator.setInterpolator(LINEAR_INTERPOLATOR);
+            mAnimator.start();
+        } else {
+            mOpacity = newOpacity;
         }
-
-        return set;
     }
 
-    @Override
-    protected void jumpValuesToExit() {
-        mOpacity = 0;
+    public void jumpToFinal() {
+        if (mAnimator != null) {
+            mAnimator.end();
+            mAnimator = null;
+        }
     }
 
     private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
diff --git a/android/graphics/drawable/RippleComponent.java b/android/graphics/drawable/RippleComponent.java
index e83513c..0e38826 100644
--- a/android/graphics/drawable/RippleComponent.java
+++ b/android/graphics/drawable/RippleComponent.java
@@ -27,23 +27,14 @@
 import java.util.ArrayList;
 
 /**
- * Abstract class that handles hardware/software hand-off and lifecycle for
- * animated ripple foreground and background components.
+ * Abstract class that handles size & positioning common to the ripple & focus states.
  */
 abstract class RippleComponent {
-    private final RippleDrawable mOwner;
+    protected final RippleDrawable mOwner;
 
     /** Bounds used for computing max radius. May be modified by the owner. */
     protected final Rect mBounds;
 
-    /** Whether we can use hardware acceleration for the exit animation. */
-    private boolean mHasDisplayListCanvas;
-
-    private boolean mHasPendingHardwareAnimator;
-    private RenderNodeAnimatorSet mHardwareAnimator;
-
-    private Animator mSoftwareAnimator;
-
     /** Whether we have an explicit maximum radius. */
     private boolean mHasMaxRadius;
 
@@ -53,16 +44,9 @@
     /** Screen density used to adjust pixel-based constants. */
     protected float mDensityScale;
 
-    /**
-     * If set, force all ripple animations to not run on RenderThread, even if it would be
-     * available.
-     */
-    private final boolean mForceSoftware;
-
-    public RippleComponent(RippleDrawable owner, Rect bounds, boolean forceSoftware) {
+    public RippleComponent(RippleDrawable owner, Rect bounds) {
         mOwner = owner;
         mBounds = bounds;
-        mForceSoftware = forceSoftware;
     }
 
     public void onBoundsChange() {
@@ -92,89 +76,6 @@
     }
 
     /**
-     * Starts a ripple enter animation.
-     *
-     * @param fast whether the ripple should enter quickly
-     */
-    public final void enter(boolean fast) {
-        cancel();
-
-        mSoftwareAnimator = createSoftwareEnter(fast);
-
-        if (mSoftwareAnimator != null) {
-            mSoftwareAnimator.start();
-        }
-    }
-
-    /**
-     * Starts a ripple exit animation.
-     */
-    public final void exit() {
-        cancel();
-
-        if (mHasDisplayListCanvas) {
-            // We don't have access to a canvas here, but we expect one on the
-            // next frame. We'll start the render thread animation then.
-            mHasPendingHardwareAnimator = true;
-
-            // Request another frame.
-            invalidateSelf();
-        } else {
-            mSoftwareAnimator = createSoftwareExit();
-            mSoftwareAnimator.start();
-        }
-    }
-
-    /**
-     * Cancels all animations. Software animation values are left in the
-     * current state, while hardware animation values jump to the end state.
-     */
-    public void cancel() {
-        cancelSoftwareAnimations();
-        endHardwareAnimations();
-    }
-
-    /**
-     * Ends all animations, jumping values to the end state.
-     */
-    public void end() {
-        endSoftwareAnimations();
-        endHardwareAnimations();
-    }
-
-    /**
-     * Draws the ripple to the canvas, inheriting the paint's color and alpha
-     * properties.
-     *
-     * @param c the canvas to which the ripple should be drawn
-     * @param p the paint used to draw the ripple
-     * @return {@code true} if something was drawn, {@code false} otherwise
-     */
-    public boolean draw(Canvas c, Paint p) {
-        final boolean hasDisplayListCanvas = !mForceSoftware && c.isHardwareAccelerated()
-                && c instanceof DisplayListCanvas;
-        if (mHasDisplayListCanvas != hasDisplayListCanvas) {
-            mHasDisplayListCanvas = hasDisplayListCanvas;
-
-            if (!hasDisplayListCanvas) {
-                // We've switched from hardware to non-hardware mode. Panic.
-                endHardwareAnimations();
-            }
-        }
-
-        if (hasDisplayListCanvas) {
-            final DisplayListCanvas hw = (DisplayListCanvas) c;
-            startPendingAnimation(hw, p);
-
-            if (mHardwareAnimator != null) {
-                return drawHardware(hw);
-            }
-        }
-
-        return drawSoftware(c, p);
-    }
-
-    /**
      * Populates {@code bounds} with the maximum drawing bounds of the ripple
      * relative to its center. The resulting bounds should be translated into
      * parent drawable coordinates before use.
@@ -186,77 +87,10 @@
         bounds.set(-r, -r, r, r);
     }
 
-    /**
-     * Starts the pending hardware animation, if available.
-     *
-     * @param hw hardware canvas on which the animation should draw
-     * @param p paint whose properties the hardware canvas should use
-     */
-    private void startPendingAnimation(DisplayListCanvas hw, Paint p) {
-        if (mHasPendingHardwareAnimator) {
-            mHasPendingHardwareAnimator = false;
-
-            mHardwareAnimator = createHardwareExit(new Paint(p));
-            mHardwareAnimator.start(hw);
-
-            // Preemptively jump the software values to the end state now that
-            // the hardware exit has read whatever values it needs.
-            jumpValuesToExit();
-        }
-    }
-
-    /**
-     * Cancels any current software animations, leaving the values in their
-     * current state.
-     */
-    private void cancelSoftwareAnimations() {
-        if (mSoftwareAnimator != null) {
-            mSoftwareAnimator.cancel();
-            mSoftwareAnimator = null;
-        }
-    }
-
-    /**
-     * Ends any current software animations, jumping the values to their end
-     * state.
-     */
-    private void endSoftwareAnimations() {
-        if (mSoftwareAnimator != null) {
-            mSoftwareAnimator.end();
-            mSoftwareAnimator = null;
-        }
-    }
-
-    /**
-     * Ends any pending or current hardware animations.
-     * <p>
-     * Hardware animations can't synchronize values back to the software
-     * thread, so there is no "cancel" equivalent.
-     */
-    private void endHardwareAnimations() {
-        if (mHardwareAnimator != null) {
-            mHardwareAnimator.end();
-            mHardwareAnimator = null;
-        }
-
-        if (mHasPendingHardwareAnimator) {
-            mHasPendingHardwareAnimator = false;
-
-            // Manually jump values to their exited state. Normally we'd do that
-            // later when starting the hardware exit, but we're aborting early.
-            jumpValuesToExit();
-        }
-    }
-
     protected final void invalidateSelf() {
         mOwner.invalidateSelf(false);
     }
 
-    protected final boolean isHardwareAnimating() {
-        return mHardwareAnimator != null && mHardwareAnimator.isRunning()
-                || mHasPendingHardwareAnimator;
-    }
-
     protected final void onHotspotBoundsChanged() {
         if (!mHasMaxRadius) {
             final float halfWidth = mBounds.width() / 2.0f;
@@ -276,76 +110,4 @@
     protected void onTargetRadiusChanged(float targetRadius) {
         // Stub.
     }
-
-    protected abstract Animator createSoftwareEnter(boolean fast);
-
-    protected abstract Animator createSoftwareExit();
-
-    protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p);
-
-    protected abstract boolean drawHardware(DisplayListCanvas c);
-
-    protected abstract boolean drawSoftware(Canvas c, Paint p);
-
-    /**
-     * Called when the hardware exit is cancelled. Jumps software values to end
-     * state to ensure that software and hardware values are synchronized.
-     */
-    protected abstract void jumpValuesToExit();
-
-    public static class RenderNodeAnimatorSet {
-        private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>();
-
-        public void add(RenderNodeAnimator anim) {
-            mAnimators.add(anim);
-        }
-
-        public void clear() {
-            mAnimators.clear();
-        }
-
-        public void start(DisplayListCanvas target) {
-            if (target == null) {
-                throw new IllegalArgumentException("Hardware canvas must be non-null");
-            }
-
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                anim.setTarget(target);
-                anim.start();
-            }
-        }
-
-        public void cancel() {
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                anim.cancel();
-            }
-        }
-
-        public void end() {
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                anim.end();
-            }
-        }
-
-        public boolean isRunning() {
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                if (anim.isRunning()) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
 }
diff --git a/android/graphics/drawable/RippleDrawable.java b/android/graphics/drawable/RippleDrawable.java
index 1727eca..8b185f2 100644
--- a/android/graphics/drawable/RippleDrawable.java
+++ b/android/graphics/drawable/RippleDrawable.java
@@ -16,11 +16,6 @@
 
 package android.graphics.drawable;
 
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.ActivityInfo.Config;
@@ -42,6 +37,11 @@
 import android.graphics.Shader;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.util.Arrays;
 
@@ -135,9 +135,6 @@
     private PorterDuffColorFilter mMaskColorFilter;
     private boolean mHasValidMask;
 
-    /** Whether we expect to draw a background when visible. */
-    private boolean mBackgroundActive;
-
     /** The current ripple. May be actively animating or pending entry. */
     private RippleForeground mRipple;
 
@@ -217,7 +214,7 @@
         }
 
         if (mBackground != null) {
-            mBackground.end();
+            mBackground.jumpToFinal();
         }
 
         cancelExitingRipples();
@@ -266,9 +263,9 @@
             }
         }
 
-        setRippleActive(focused || (enabled && pressed));
+        setRippleActive(enabled && pressed);
 
-        setBackgroundActive(hovered, hovered);
+        setBackgroundActive(hovered, focused);
         return changed;
     }
 
@@ -283,14 +280,13 @@
         }
     }
 
-    private void setBackgroundActive(boolean active, boolean focused) {
-        if (mBackgroundActive != active) {
-            mBackgroundActive = active;
-            if (active) {
-                tryBackgroundEnter(focused);
-            } else {
-                tryBackgroundExit();
-            }
+    private void setBackgroundActive(boolean hovered, boolean focused) {
+        if (mBackground == null && (hovered || focused)) {
+            mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
+            mBackground.setup(mState.mMaxRadius, mDensity);
+        }
+        if (mBackground != null) {
+            mBackground.setState(focused, hovered, true);
         }
     }
 
@@ -327,10 +323,6 @@
                 tryRippleEnter();
             }
 
-            if (mBackgroundActive) {
-                tryBackgroundEnter(false);
-            }
-
             // Skip animations, just show the correct final states.
             jumpToCurrentState();
         }
@@ -546,26 +538,6 @@
     }
 
     /**
-     * Creates an active hotspot at the specified location.
-     */
-    private void tryBackgroundEnter(boolean focused) {
-        if (mBackground == null) {
-            final boolean isBounded = isBounded();
-            mBackground = new RippleBackground(this, mHotspotBounds, isBounded, mForceSoftware);
-        }
-
-        mBackground.setup(mState.mMaxRadius, mDensity);
-        mBackground.enter(focused);
-    }
-
-    private void tryBackgroundExit() {
-        if (mBackground != null) {
-            // Don't null out the background, we need it to draw!
-            mBackground.exit();
-        }
-    }
-
-    /**
      * Attempts to start an enter animation for the active hotspot. Fails if
      * there are too many animating ripples.
      */
@@ -593,7 +565,7 @@
         }
 
         mRipple.setup(mState.mMaxRadius, mDensity);
-        mRipple.enter(false);
+        mRipple.enter();
     }
 
     /**
@@ -623,9 +595,7 @@
         }
 
         if (mBackground != null) {
-            mBackground.end();
-            mBackground = null;
-            mBackgroundActive = false;
+            mBackground.setState(false, false, false);
         }
 
         cancelExitingRipples();
@@ -858,38 +828,8 @@
         final float y = mHotspotBounds.exactCenterY();
         canvas.translate(x, y);
 
-        updateMaskShaderIfNeeded();
-
-        // Position the shader to account for canvas translation.
-        if (mMaskShader != null) {
-            final Rect bounds = getBounds();
-            mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
-            mMaskShader.setLocalMatrix(mMaskMatrix);
-        }
-
-        // Grab the color for the current state and cut the alpha channel in
-        // half so that the ripple and background together yield full alpha.
-        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
-        final int halfAlpha = (Color.alpha(color) / 2) << 24;
         final Paint p = getRipplePaint();
 
-        if (mMaskColorFilter != null) {
-            // The ripple timing depends on the paint's alpha value, so we need
-            // to push just the alpha channel into the paint and let the filter
-            // handle the full-alpha color.
-            final int fullAlphaColor = color | (0xFF << 24);
-            mMaskColorFilter.setColor(fullAlphaColor);
-
-            p.setColor(halfAlpha);
-            p.setColorFilter(mMaskColorFilter);
-            p.setShader(mMaskShader);
-        } else {
-            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
-            p.setColor(halfAlphaColor);
-            p.setColorFilter(null);
-            p.setShader(null);
-        }
-
         if (background != null && background.isVisible()) {
             background.draw(canvas, p);
         }
@@ -912,13 +852,49 @@
         mMask.draw(canvas);
     }
 
-    private Paint getRipplePaint() {
+    Paint getRipplePaint() {
         if (mRipplePaint == null) {
             mRipplePaint = new Paint();
             mRipplePaint.setAntiAlias(true);
             mRipplePaint.setStyle(Paint.Style.FILL);
         }
-        return mRipplePaint;
+
+        final float x = mHotspotBounds.exactCenterX();
+        final float y = mHotspotBounds.exactCenterY();
+
+        updateMaskShaderIfNeeded();
+
+        // Position the shader to account for canvas translation.
+        if (mMaskShader != null) {
+            final Rect bounds = getBounds();
+            mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
+            mMaskShader.setLocalMatrix(mMaskMatrix);
+        }
+
+        // Grab the color for the current state and cut the alpha channel in
+        // half so that the ripple and background together yield full alpha.
+        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
+        final int halfAlpha = (Color.alpha(color) / 2) << 24;
+        final Paint p = mRipplePaint;
+
+        if (mMaskColorFilter != null) {
+            // The ripple timing depends on the paint's alpha value, so we need
+            // to push just the alpha channel into the paint and let the filter
+            // handle the full-alpha color.
+            final int fullAlphaColor = color | (0xFF << 24);
+            mMaskColorFilter.setColor(fullAlphaColor);
+
+            p.setColor(halfAlpha);
+            p.setColorFilter(mMaskColorFilter);
+            p.setShader(mMaskShader);
+        } else {
+            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
+            p.setColor(halfAlphaColor);
+            p.setColorFilter(null);
+            p.setShader(null);
+        }
+
+        return p;
     }
 
     @Override
diff --git a/android/graphics/drawable/RippleForeground.java b/android/graphics/drawable/RippleForeground.java
index a675eaf..0b5020c 100644
--- a/android/graphics/drawable/RippleForeground.java
+++ b/android/graphics/drawable/RippleForeground.java
@@ -18,7 +18,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
@@ -29,8 +28,11 @@
 import android.util.MathUtils;
 import android.view.DisplayListCanvas;
 import android.view.RenderNodeAnimator;
+import android.view.animation.AnimationUtils;
 import android.view.animation.LinearInterpolator;
 
+import java.util.ArrayList;
+
 /**
  * Draws a ripple foreground.
  */
@@ -40,7 +42,7 @@
             400f, 1.4f, 0);
 
     // Pixel-based accelerations and velocities.
-    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
+    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 2048;
     private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
 
     // Bounded ripple animation properties.
@@ -49,8 +51,9 @@
     private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
     private static final float MAX_BOUNDED_RADIUS = 350;
 
-    private static final int RIPPLE_ENTER_DELAY = 80;
-    private static final int OPACITY_ENTER_DURATION_FAST = 120;
+    private static final int OPACITY_ENTER_DURATION = 75;
+    private static final int OPACITY_EXIT_DURATION = 150;
+    private static final int OPACITY_HOLD_DURATION = OPACITY_ENTER_DURATION + 150;
 
     // Parent-relative values for starting position.
     private float mStartingX;
@@ -72,7 +75,7 @@
     private float mBoundedRadius = 0;
 
     // Software rendering properties.
-    private float mOpacity = 1;
+    private float mOpacity = 0;
 
     // Values used to tween between the start and end positions.
     private float mTweenRadius = 0;
@@ -82,6 +85,22 @@
     /** Whether this ripple has finished its exit animation. */
     private boolean mHasFinishedExit;
 
+    /** Whether we can use hardware acceleration for the exit animation. */
+    private boolean mUsingProperties;
+
+    private long mEnterStartedAtMillis;
+
+    private ArrayList<RenderNodeAnimator> mPendingHwAnimators = new ArrayList<>();
+    private ArrayList<RenderNodeAnimator> mRunningHwAnimators = new ArrayList<>();
+
+    private ArrayList<Animator> mRunningSwAnimators = new ArrayList<>();
+
+    /**
+     * If set, force all ripple animations to not run on RenderThread, even if it would be
+     * available.
+     */
+    private final boolean mForceSoftware;
+
     /**
      * If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
      */
@@ -89,8 +108,9 @@
 
     public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
             boolean isBounded, boolean forceSoftware) {
-        super(owner, bounds, forceSoftware);
+        super(owner, bounds);
 
+        mForceSoftware = forceSoftware;
         mStartingX = startingX;
         mStartingY = startingY;
 
@@ -109,10 +129,7 @@
         clampStartingPosition();
     }
 
-    @Override
-    protected boolean drawSoftware(Canvas c, Paint p) {
-        boolean hasContent = false;
-
+    private void drawSoftware(Canvas c, Paint p) {
         final int origAlpha = p.getAlpha();
         final int alpha = (int) (origAlpha * mOpacity + 0.5f);
         final float radius = getCurrentRadius();
@@ -122,16 +139,51 @@
             p.setAlpha(alpha);
             c.drawCircle(x, y, radius, p);
             p.setAlpha(origAlpha);
-            hasContent = true;
         }
-
-        return hasContent;
     }
 
-    @Override
-    protected boolean drawHardware(DisplayListCanvas c) {
-        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
-        return true;
+    private void startPending(DisplayListCanvas c) {
+        if (!mPendingHwAnimators.isEmpty()) {
+            for (int i = 0; i < mPendingHwAnimators.size(); i++) {
+                RenderNodeAnimator animator = mPendingHwAnimators.get(i);
+                animator.setTarget(c);
+                animator.start();
+                mRunningHwAnimators.add(animator);
+            }
+            mPendingHwAnimators.clear();
+        }
+    }
+
+    private void pruneHwFinished() {
+        if (!mRunningHwAnimators.isEmpty()) {
+            for (int i = mRunningHwAnimators.size() - 1; i >= 0; i--) {
+                if (!mRunningHwAnimators.get(i).isRunning()) {
+                    mRunningHwAnimators.remove(i);
+                }
+            }
+        }
+    }
+
+    private void pruneSwFinished() {
+        if (!mRunningSwAnimators.isEmpty()) {
+            for (int i = mRunningSwAnimators.size() - 1; i >= 0; i--) {
+                if (!mRunningSwAnimators.get(i).isRunning()) {
+                    mRunningSwAnimators.remove(i);
+                }
+            }
+        }
+    }
+
+    private void drawHardware(DisplayListCanvas c, Paint p) {
+        startPending(c);
+        pruneHwFinished();
+        if (mPropPaint != null) {
+            mUsingProperties = true;
+            c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+        } else {
+            mUsingProperties = false;
+            drawSoftware(c, p);
+        }
     }
 
     /**
@@ -162,31 +214,115 @@
         return mHasFinishedExit;
     }
 
-    @Override
-    protected Animator createSoftwareEnter(boolean fast) {
+    private long computeFadeOutDelay() {
+        long timeSinceEnter = AnimationUtils.currentAnimationTimeMillis() - mEnterStartedAtMillis;
+        if (timeSinceEnter > 0 && timeSinceEnter < OPACITY_HOLD_DURATION) {
+            return OPACITY_HOLD_DURATION - timeSinceEnter;
+        }
+        return 0;
+    }
+
+    private void startSoftwareEnter() {
+        for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+            mRunningSwAnimators.get(i).cancel();
+        }
+        mRunningSwAnimators.clear();
+
         final int duration = getRadiusDuration();
 
         final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
-        tweenRadius.setAutoCancel(true);
         tweenRadius.setDuration(duration);
         tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
-        tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
+        tweenRadius.start();
+        mRunningSwAnimators.add(tweenRadius);
 
         final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
-        tweenOrigin.setAutoCancel(true);
         tweenOrigin.setDuration(duration);
         tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
-        tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
+        tweenOrigin.start();
+        mRunningSwAnimators.add(tweenOrigin);
 
         final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(OPACITY_ENTER_DURATION_FAST);
+        opacity.setDuration(OPACITY_ENTER_DURATION);
         opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.start();
+        mRunningSwAnimators.add(opacity);
+    }
 
-        final AnimatorSet set = new AnimatorSet();
-        set.play(tweenOrigin).with(tweenRadius).with(opacity);
+    private void startSoftwareExit() {
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
+        opacity.setDuration(OPACITY_EXIT_DURATION);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.addListener(mAnimationListener);
+        opacity.setStartDelay(computeFadeOutDelay());
+        opacity.start();
+        mRunningSwAnimators.add(opacity);
+    }
 
-        return set;
+    private void startHardwareEnter() {
+        if (mForceSoftware) { return; }
+        mPropX = CanvasProperty.createFloat(getCurrentX());
+        mPropY = CanvasProperty.createFloat(getCurrentY());
+        mPropRadius = CanvasProperty.createFloat(getCurrentRadius());
+        final Paint paint = mOwner.getRipplePaint();
+        mPropPaint = CanvasProperty.createPaint(paint);
+
+        final int radiusDuration = getRadiusDuration();
+
+        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
+        radius.setDuration(radiusDuration);
+        radius.setInterpolator(DECELERATE_INTERPOLATOR);
+        mPendingHwAnimators.add(radius);
+
+        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
+        x.setDuration(radiusDuration);
+        x.setInterpolator(DECELERATE_INTERPOLATOR);
+        mPendingHwAnimators.add(x);
+
+        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
+        y.setDuration(radiusDuration);
+        y.setInterpolator(DECELERATE_INTERPOLATOR);
+        mPendingHwAnimators.add(y);
+
+        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+                RenderNodeAnimator.PAINT_ALPHA, paint.getAlpha());
+        opacity.setDuration(OPACITY_ENTER_DURATION);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.setStartValue(0);
+        mPendingHwAnimators.add(opacity);
+
+        invalidateSelf();
+    }
+
+    private void startHardwareExit() {
+        // Only run a hardware exit if we had a hardware enter to continue from
+        if (mForceSoftware || mPropPaint == null) return;
+
+        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+                RenderNodeAnimator.PAINT_ALPHA, 0);
+        opacity.setDuration(OPACITY_EXIT_DURATION);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.addListener(mAnimationListener);
+        opacity.setStartDelay(computeFadeOutDelay());
+        mPendingHwAnimators.add(opacity);
+        invalidateSelf();
+    }
+
+    /**
+     * Starts a ripple enter animation.
+     */
+    public final void enter() {
+        mEnterStartedAtMillis = AnimationUtils.currentAnimationTimeMillis();
+        startSoftwareEnter();
+        startHardwareEnter();
+    }
+
+    /**
+     * Starts a ripple exit animation.
+     */
+    public final void exit() {
+        startSoftwareExit();
+        startHardwareExit();
     }
 
     private float getCurrentX() {
@@ -207,96 +343,23 @@
         return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
     }
 
-    private int getOpacityExitDuration() {
-        return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
-    }
+    /**
+     * Draws the ripple to the canvas, inheriting the paint's color and alpha
+     * properties.
+     *
+     * @param c the canvas to which the ripple should be drawn
+     * @param p the paint used to draw the ripple
+     */
+    public void draw(Canvas c, Paint p) {
+        final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof DisplayListCanvas;
 
-    @Override
-    protected Animator createSoftwareExit() {
-        final int radiusDuration;
-        final int originDuration;
-        final int opacityDuration;
-
-        radiusDuration = getRadiusDuration();
-        originDuration = radiusDuration;
-        opacityDuration = getOpacityExitDuration();
-
-        final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
-        tweenRadius.setAutoCancel(true);
-        tweenRadius.setDuration(radiusDuration);
-        tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
-        tweenOrigin.setAutoCancel(true);
-        tweenOrigin.setDuration(originDuration);
-        tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(opacityDuration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
-        final AnimatorSet set = new AnimatorSet();
-        set.play(tweenOrigin).with(tweenRadius).with(opacity);
-        set.addListener(mAnimationListener);
-
-        return set;
-    }
-
-    @Override
-    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
-        final int radiusDuration;
-        final int originDuration;
-        final int opacityDuration;
-
-        radiusDuration = getRadiusDuration();
-        originDuration = radiusDuration;
-        opacityDuration = getOpacityExitDuration();
-
-        final float startX = getCurrentX();
-        final float startY = getCurrentY();
-        final float startRadius = getCurrentRadius();
-
-        p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
-
-        mPropPaint = CanvasProperty.createPaint(p);
-        mPropRadius = CanvasProperty.createFloat(startRadius);
-        mPropX = CanvasProperty.createFloat(startX);
-        mPropY = CanvasProperty.createFloat(startY);
-
-        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
-        radius.setDuration(radiusDuration);
-        radius.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
-        x.setDuration(originDuration);
-        x.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
-        y.setDuration(originDuration);
-        y.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
-                RenderNodeAnimator.PAINT_ALPHA, 0);
-        opacity.setDuration(opacityDuration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
-        opacity.addListener(mAnimationListener);
-
-        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
-        set.add(radius);
-        set.add(opacity);
-        set.add(x);
-        set.add(y);
-
-        return set;
-    }
-
-    @Override
-    protected void jumpValuesToExit() {
-        mOpacity = 0;
-        mTweenX = 1;
-        mTweenY = 1;
-        mTweenRadius = 1;
+        pruneSwFinished();
+        if (hasDisplayListCanvas) {
+            final DisplayListCanvas hw = (DisplayListCanvas) c;
+            drawHardware(hw, p);
+        } else {
+            drawSoftware(c, p);
+        }
     }
 
     /**
@@ -319,10 +382,39 @@
         }
     }
 
+    /**
+     * Ends all animations, jumping values to the end state.
+     */
+    public void end() {
+        for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+            mRunningSwAnimators.get(i).end();
+        }
+        mRunningSwAnimators.clear();
+        for (int i = 0; i < mRunningHwAnimators.size(); i++) {
+            mRunningHwAnimators.get(i).end();
+        }
+        mRunningHwAnimators.clear();
+    }
+
+    private void onAnimationPropertyChanged() {
+        if (!mUsingProperties) {
+            invalidateSelf();
+        }
+    }
+
     private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animator) {
             mHasFinishedExit = true;
+            pruneHwFinished();
+            pruneSwFinished();
+
+            if (mRunningHwAnimators.isEmpty()) {
+                mPropPaint = null;
+                mPropRadius = null;
+                mPropX = null;
+                mPropY = null;
+            }
         }
     };
 
@@ -361,7 +453,7 @@
         @Override
         public void setValue(RippleForeground object, float value) {
             object.mTweenRadius = value;
-            object.invalidateSelf();
+            object.onAnimationPropertyChanged();
         }
 
         @Override
@@ -375,18 +467,18 @@
      */
     private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
             new FloatProperty<RippleForeground>("tweenOrigin") {
-                @Override
-                public void setValue(RippleForeground object, float value) {
-                    object.mTweenX = value;
-                    object.mTweenY = value;
-                    object.invalidateSelf();
-                }
+        @Override
+        public void setValue(RippleForeground object, float value) {
+            object.mTweenX = value;
+            object.mTweenY = value;
+            object.onAnimationPropertyChanged();
+        }
 
-                @Override
-                public Float get(RippleForeground object) {
-                    return object.mTweenX;
-                }
-            };
+        @Override
+        public Float get(RippleForeground object) {
+            return object.mTweenX;
+        }
+    };
 
     /**
      * Property for animating opacity between 0 and its target value.
@@ -396,7 +488,7 @@
         @Override
         public void setValue(RippleForeground object, float value) {
             object.mOpacity = value;
-            object.invalidateSelf();
+            object.onAnimationPropertyChanged();
         }
 
         @Override
diff --git a/android/graphics/drawable/VectorDrawable.java b/android/graphics/drawable/VectorDrawable.java
index ceac325..7b2e21a 100644
--- a/android/graphics/drawable/VectorDrawable.java
+++ b/android/graphics/drawable/VectorDrawable.java
@@ -213,12 +213,79 @@
  * &lt;/vector&gt;
  * </pre>
  * </li>
- * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
+ * <h4>Gradient support</h4>
+ * We support 3 types of gradients: {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ * <p/>
+ * And we support all of 3 types of tile modes {@link android.graphics.Shader.TileMode}:
+ * CLAMP, REPEAT, MIRROR.
+ * <p/>
+ * All of the attributes are listed in {@link android.R.styleable#GradientColor}.
+ * Note that different attributes are relevant for different types of gradient.
+ * <table border="2" align="center" cellpadding="5">
+ *     <thead>
+ *         <tr>
+ *             <th>LinearGradient</th>
+ *             <th>RadialGradient</th>
+ *             <th>SweepGradient</th>
+ *         </tr>
+ *     </thead>
+ *     <tr>
+ *         <td>startColor </td>
+ *         <td>startColor</td>
+ *         <td>startColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>centerColor</td>
+ *         <td>centerColor</td>
+ *         <td>centerColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>endColor</td>
+ *         <td>endColor</td>
+ *         <td>endColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>type</td>
+ *         <td>type</td>
+ *         <td>type</td>
+ *     </tr>
+ *     <tr>
+ *         <td>tileMode</td>
+ *         <td>tileMode</td>
+ *         <td>tileMode</td>
+ *     </tr>
+ *     <tr>
+ *         <td>startX</td>
+ *         <td>centerX</td>
+ *         <td>centerX</td>
+ *     </tr>
+ *     <tr>
+ *         <td>startY</td>
+ *         <td>centerY</td>
+ *         <td>centerY</td>
+ *     </tr>
+ *     <tr>
+ *         <td>endX</td>
+ *         <td>gradientRadius</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>endY</td>
+ *         <td></td>
+ *         <td></td>
+ *     </tr>
+ * </table>
+ * <p/>
+ * Also note that if any color item {@link android.R.styleable#GradientColorItem} is defined, then
+ * startColor, centerColor and endColor will be ignored.
+ * <p/>
  * See more details in {@link android.R.styleable#GradientColor} and
  * {@link android.R.styleable#GradientColorItem}.
+ * <p/>
+ * Here is a simple example that defines a linear gradient.
  * <pre>
  * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
- *     android:angle="90"
  *     android:startColor="?android:attr/colorPrimary"
  *     android:endColor="?android:attr/colorControlActivated"
  *     android:centerColor="#f00"
@@ -229,7 +296,18 @@
  *     android:type="linear"&gt;
  * &lt;/gradient&gt;
  * </pre>
- * </li>
+ * And here is a simple example that defines a radial gradient using color items.
+ * <pre>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:centerX="300"
+ *     android:centerY="300"
+ *     android:gradientRadius="100"
+ *     android:type="radial"&gt;
+ *     &lt;item android:offset="0.1" android:color="#0ff"/&gt;
+ *     &lt;item android:offset="0.4" android:color="#fff"/&gt;
+ *     &lt;item android:offset="0.9" android:color="#ff0"/&gt;
+ * &lt;/gradient&gt;
+ * </pre>
  *
  */
 
diff --git a/android/hardware/camera2/CameraCaptureSession.java b/android/hardware/camera2/CameraCaptureSession.java
index da771e4..ff69bd8 100644
--- a/android/hardware/camera2/CameraCaptureSession.java
+++ b/android/hardware/camera2/CameraCaptureSession.java
@@ -249,7 +249,7 @@
      * <p>This function can also be called in case where multiple surfaces share the same
      * OutputConfiguration, and one of the surfaces becomes available after the {@link
      * CameraCaptureSession} is created. In that case, the application must first create the
-     * OutputConfiguration with the available Surface, then enable furture surface sharing via
+     * OutputConfiguration with the available Surface, then enable further surface sharing via
      * {@link OutputConfiguration#enableSurfaceSharing}, before creating the CameraCaptureSession.
      * After the CameraCaptureSession is created, and once the extra Surface becomes available, the
      * application must then call {@link OutputConfiguration#addSurface} before finalizing the
@@ -645,6 +645,44 @@
     public abstract Surface getInputSurface();
 
     /**
+     * Update {@link OutputConfiguration} after configuration finalization see
+     * {@link #finalizeOutputConfigurations}.
+     *
+     * <p>Any {@link OutputConfiguration} that has been modified via calls to
+     * {@link OutputConfiguration#addSurface} or {@link OutputConfiguration#removeSurface} must be
+     * updated. After the update call returns without throwing exceptions any newly added surfaces
+     * can be referenced in subsequent capture requests.</p>
+     *
+     * <p>Surfaces that get removed must not be part of any active repeating or single/burst
+     * request or have any pending results. Consider updating any repeating requests first via
+     * {@link #setRepeatingRequest} or {@link #setRepeatingBurst} and then wait for the last frame
+     * number when the sequence completes {@link CaptureCallback#onCaptureSequenceCompleted}
+     * before calling updateOutputConfiguration to remove a previously active Surface.</p>
+     *
+     * <p>Surfaces that get added must not be part of any other registered
+     * {@link OutputConfiguration}.</p>
+     *
+     * @param config Modified output configuration.
+     *
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error.
+     * @throws IllegalArgumentException if an attempt was made to add a {@link Surface} already
+     *                               in use by another buffer-producing API, such as MediaCodec or
+     *                               a different camera device or {@link OutputConfiguration}; or
+     *                               new surfaces are not compatible (see
+     *                               {@link OutputConfiguration#enableSurfaceSharing}); or a
+     *                               {@link Surface} that was removed from the modified
+     *                               {@link OutputConfiguration} still has pending requests.
+     * @throws IllegalStateException if this session is no longer active, either because the session
+     *                               was explicitly closed, a new session has been created
+     *                               or the camera device has been closed.
+     */
+    public void updateOutputConfiguration(OutputConfiguration config)
+        throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
      * Close this capture session asynchronously.
      *
      * <p>Closing a session frees up the target output Surfaces of the session for reuse with either
diff --git a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index c7654c9..374789c 100644
--- a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -314,6 +314,20 @@
     }
 
     @Override
+    public void updateOutputConfiguration(OutputConfiguration config)
+            throws CameraAccessException {
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            checkNotClosed();
+
+            if (DEBUG) {
+                Log.v(TAG, mIdString + "updateOutputConfiguration");
+            }
+
+            mDeviceImpl.updateOutputConfiguration(config);
+        }
+    }
+
+    @Override
     public boolean isReprocessable() {
         return mInput != null;
     }
diff --git a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index fec7fd9..8c4dbfa 100644
--- a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -235,6 +235,13 @@
     }
 
     @Override
+    public void updateOutputConfiguration(OutputConfiguration config)
+            throws CameraAccessException {
+        throw new UnsupportedOperationException("Constrained high speed session doesn't support"
+                + " this method");
+    }
+
+    @Override
     public void close() {
         mSessionImpl.close();
     }
diff --git a/android/hardware/camera2/impl/CameraDeviceImpl.java b/android/hardware/camera2/impl/CameraDeviceImpl.java
index bfeb14d..6787d84 100644
--- a/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -764,6 +764,24 @@
         }
     }
 
+    public void updateOutputConfiguration(OutputConfiguration config)
+            throws CameraAccessException {
+        synchronized(mInterfaceLock) {
+            int streamId = -1;
+            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+                if (config.getSurface() == mConfiguredOutputs.valueAt(i).getSurface()) {
+                    streamId = mConfiguredOutputs.keyAt(i);
+                    break;
+                }
+            }
+            if (streamId == -1) {
+                throw new IllegalArgumentException("Invalid output configuration");
+            }
+
+            mRemoteDevice.updateOutputConfiguration(streamId, config);
+        }
+    }
+
     public void tearDown(Surface surface) throws CameraAccessException {
         if (surface == null) throw new IllegalArgumentException("Surface is null");
 
diff --git a/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 27087a2..0978ff8 100644
--- a/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -215,6 +215,16 @@
         }
     }
 
+    public void updateOutputConfiguration(int streamId, OutputConfiguration config)
+            throws CameraAccessException {
+        try {
+            mRemoteDevice.updateOutputConfiguration(streamId, config);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
     public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
             throws CameraAccessException {
         try {
diff --git a/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 49d4096..119cca8 100644
--- a/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -646,6 +646,11 @@
     }
 
     @Override
+    public void updateOutputConfiguration(int streamId, OutputConfiguration config) {
+        // TODO: b/63912484 implement updateOutputConfiguration.
+    }
+
+    @Override
     public void waitUntilIdle() throws RemoteException {
         if (DEBUG) {
             Log.d(TAG, "waitUntilIdle called.");
diff --git a/android/hardware/camera2/params/OutputConfiguration.java b/android/hardware/camera2/params/OutputConfiguration.java
index 2b317d6..7409671 100644
--- a/android/hardware/camera2/params/OutputConfiguration.java
+++ b/android/hardware/camera2/params/OutputConfiguration.java
@@ -42,6 +42,53 @@
  * A class for describing camera output, which contains a {@link Surface} and its specific
  * configuration for creating capture session.
  *
+ * <p>There are several ways to instantiate, modify and use OutputConfigurations. The most common
+ * and recommended usage patterns are summarized in the following list:</p>
+ *<ul>
+ * <li>Passing a {@link Surface} to the constructor and using the OutputConfiguration instance as
+ * argument to {@link CameraDevice#createCaptureSessionByOutputConfigurations}. This is the most
+ * frequent usage and clients should consider it first before other more complicated alternatives.
+ * </li>
+ *
+ * <li>Passing only a surface source class as an argument to the constructor. This is usually
+ * followed by a call to create a capture session
+ * (see {@link CameraDevice#createCaptureSessionByOutputConfigurations} and a {@link Surface} add
+ * call {@link #addSurface} with a valid {@link Surface}. The sequence completes with
+ * {@link CameraCaptureSession#finalizeOutputConfigurations}. This is the deferred usage case which
+ * aims to enhance performance by allowing the resource-intensive capture session create call to
+ * execute in parallel with any {@link Surface} initialization, such as waiting for a
+ * {@link android.view.SurfaceView} to be ready as part of the UI initialization.</li>
+ *
+ * <li>The third and most complex usage pattern inlvolves surface sharing. Once instantiated an
+ * OutputConfiguration can be enabled for surface sharing via {@link #enableSurfaceSharing}. This
+ * must be done before creating a new capture session and enables calls to
+ * {@link CameraCaptureSession#updateOutputConfiguration}. An OutputConfiguration with enabled
+ * surface sharing can be modified via {@link #addSurface} or {@link #removeSurface}. The updates
+ * to this OutputConfiguration will only come into effect after
+ * {@link CameraCaptureSession#updateOutputConfiguration} returns without throwing exceptions.
+ * Such updates can be done as long as the session is active. Clients should always consider the
+ * additional requirements and limitations placed on the output surfaces (for more details see
+ * {@link #enableSurfaceSharing}, {@link #addSurface}, {@link #removeSurface},
+ * {@link CameraCaptureSession#updateOutputConfiguration}). A trade-off exists between additional
+ * complexity and flexibility. If exercised correctly surface sharing can switch between different
+ * output surfaces without interrupting any ongoing repeating capture requests. This saves time and
+ * can significantly improve the user experience.</li>
+ *
+ * <li>Surface sharing can be used in combination with deferred surfaces. The rules from both cases
+ * are combined and clients must call {@link #enableSurfaceSharing} before creating a capture
+ * session. Attach and/or remove output surfaces via  {@link #addSurface}/{@link #removeSurface} and
+ * finalize the configuration using {@link CameraCaptureSession#finalizeOutputConfigurations}.
+ * {@link CameraCaptureSession#updateOutputConfiguration} can be called after the configuration
+ * finalize method returns without exceptions.</li>
+ *
+ * </ul>
+ *
+ * <p>Please note that surface sharing is currently only enabled for outputs that use the
+ * {@link ImageFormat#PRIVATE} format. This includes surface sources like
+ * {@link android.view.SurfaceView}, {@link android.media.MediaRecorder},
+ * {@link android.graphics.SurfaceTexture} and {@link android.media.ImageReader}, configured using
+ * the aforementioned format.</p>
+ *
  * @see CameraDevice#createCaptureSessionByOutputConfigurations
  *
  */
@@ -123,7 +170,7 @@
      * {@link OutputConfiguration#addSurface} should not exceed this value.</p>
      *
      */
-    private static final int MAX_SURFACES_COUNT = 2;
+    private static final int MAX_SURFACES_COUNT = 4;
 
     /**
      * Create a new {@link OutputConfiguration} instance with a {@link Surface},
@@ -280,7 +327,10 @@
      * <p>For advanced use cases, a camera application may require more streams than the combination
      * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one
      * compatible surface can be attached to an OutputConfiguration so that they map to one
-     * camera stream, and the outputs share memory buffers when possible. </p>
+     * camera stream, and the outputs share memory buffers when possible. Due to buffer sharing
+     * clients should be careful when adding surface outputs that modify their input data. If such
+     * case exists, camera clients should have an additional mechanism to synchronize read and write
+     * access between individual consumers.</p>
      *
      * <p>Two surfaces are compatible in the below cases:</p>
      *
@@ -301,9 +351,9 @@
      * CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link
      * CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p>
      *
-     * <p>Up to 2 surfaces can be shared for an OutputConfiguration. The supported surfaces for
-     * sharing must be of type SurfaceTexture, SurfaceView, MediaRecorder, MediaCodec, or
-     * implementation defined ImageReader.</p>
+     * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration.
+     * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView,
+     * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p>
      */
     public void enableSurfaceSharing() {
         mIsShared = true;
@@ -329,8 +379,10 @@
      * <p> This function can be called before or after {@link
      * CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after,
      * the application must finalize the capture session with
-     * {@link CameraCaptureSession#finalizeOutputConfigurations}.
-     * </p>
+     * {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method
+     * after the output configurations have been finalized only in cases of enabled surface sharing
+     * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with
+     * {@link CameraCaptureSession#updateOutputConfiguration}.</p>
      *
      * <p> If the OutputConfiguration was constructed with a deferred surface by {@link
      * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
@@ -388,6 +440,31 @@
     }
 
     /**
+     * Remove a surface from this OutputConfiguration.
+     *
+     * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
+     *  OutputConfiguration. The only notable exception is the surface associated with
+     *  the OutputConfigration see {@link #getSurface} which was passed as part of the constructor
+     *  or was added first in the deferred case
+     *  {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
+     *
+     * @param surface The surface to be removed.
+     *
+     * @throws IllegalArgumentException If the surface is associated with this OutputConfiguration
+     *                                  (see {@link #getSurface}) or the surface didn't get added
+     *                                  with {@link #addSurface}.
+     */
+    public void removeSurface(@NonNull Surface surface) {
+        if (getSurface() == surface) {
+            throw new IllegalArgumentException(
+                    "Cannot remove surface associated with this output configuration");
+        }
+        if (!mSurfaces.remove(surface)) {
+            throw new IllegalArgumentException("Surface is not part of this output configuration");
+        }
+    }
+
+    /**
      * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
      * instance.
      *
@@ -447,6 +524,17 @@
     }
 
     /**
+     * Get the maximum supported shared {@link Surface} count.
+     *
+     * @return the maximum number of surfaces that can be added per each OutputConfiguration.
+     *
+     * @see #enableSurfaceSharing
+     */
+    public static int getMaxSharedSurfaceCount() {
+        return MAX_SURFACES_COUNT;
+    }
+
+    /**
      * Get the {@link Surface} associated with this {@link OutputConfiguration}.
      *
      * If more than one surface is associated with this {@link OutputConfiguration}, return the
diff --git a/android/hardware/display/BrightnessChangeEvent.java b/android/hardware/display/BrightnessChangeEvent.java
new file mode 100644
index 0000000..fe24e32
--- /dev/null
+++ b/android/hardware/display/BrightnessChangeEvent.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data about a brightness settings change.
+ * TODO make this SystemAPI
+ * @hide
+ */
+public final class BrightnessChangeEvent implements Parcelable {
+    /** Brightness in nits */
+    public int brightness;
+
+    /** Timestamp of the change {@see System.currentTimeMillis()} */
+    public long timeStamp;
+
+    /** Package name of focused activity when brightness was changed. */
+    public String packageName;
+
+    /** User id of of the user running when brightness was changed.
+     * @hide */
+    public int userId;
+
+    /** Lux values of recent sensor data */
+    public float[] luxValues;
+
+    /** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */
+    public long[] luxTimestamps;
+
+    /** Most recent battery level when brightness was changed or Float.NaN */
+    public float batteryLevel;
+
+    /** Color filter active to provide night mode */
+    public boolean nightMode;
+
+    /** If night mode color filter is active this will be the temperature in kelvin */
+    public int colorTemperature;
+
+    /** Brightness level before slider adjustment */
+    public int lastBrightness;
+
+    public BrightnessChangeEvent() {
+    }
+
+    private BrightnessChangeEvent(Parcel source) {
+        brightness = source.readInt();
+        timeStamp = source.readLong();
+        packageName = source.readString();
+        userId = source.readInt();
+        luxValues = source.createFloatArray();
+        luxTimestamps = source.createLongArray();
+        batteryLevel = source.readFloat();
+        nightMode = source.readBoolean();
+        colorTemperature = source.readInt();
+        lastBrightness = source.readInt();
+    }
+
+    public static final Creator<BrightnessChangeEvent> CREATOR =
+            new Creator<BrightnessChangeEvent>() {
+                public BrightnessChangeEvent createFromParcel(Parcel source) {
+                    return new BrightnessChangeEvent(source);
+                }
+                public BrightnessChangeEvent[] newArray(int size) {
+                    return new BrightnessChangeEvent[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(brightness);
+        dest.writeLong(timeStamp);
+        dest.writeString(packageName);
+        dest.writeInt(userId);
+        dest.writeFloatArray(luxValues);
+        dest.writeLongArray(luxTimestamps);
+        dest.writeFloat(batteryLevel);
+        dest.writeBoolean(nightMode);
+        dest.writeInt(colorTemperature);
+        dest.writeInt(lastBrightness);
+    }
+}
diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java
index b2af44e..ef77d6e 100644
--- a/android/hardware/display/DisplayManager.java
+++ b/android/hardware/display/DisplayManager.java
@@ -30,6 +30,7 @@
 import android.view.WindowManagerPolicy;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Manages the properties of attached displays.
@@ -615,6 +616,21 @@
     }
 
     /**
+     * Fetch {@link BrightnessChangeEvent}s.
+     * @hide until we make it a system api.
+     */
+    public List<BrightnessChangeEvent> getBrightnessEvents() {
+        return mGlobal.getBrightnessEvents();
+    }
+
+    /**
+     * @hide STOPSHIP - remove when adaptive brightness accepts curves.
+     */
+    public void setBrightness(int brightness) {
+        mGlobal.setBrightness(brightness);
+    }
+
+    /**
      * Listens for changes in available display devices.
      */
     public interface DisplayListener {
diff --git a/android/hardware/display/DisplayManagerGlobal.java b/android/hardware/display/DisplayManagerGlobal.java
index a8a4eb6..d93d0e4 100644
--- a/android/hardware/display/DisplayManagerGlobal.java
+++ b/android/hardware/display/DisplayManagerGlobal.java
@@ -17,6 +17,7 @@
 package android.hardware.display;
 
 import android.content.Context;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -37,6 +38,8 @@
 import android.view.Surface;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Manager communication with the display manager service on behalf of
@@ -456,6 +459,33 @@
         }
     }
 
+    /**
+     * Retrieves brightness change events.
+     */
+    public List<BrightnessChangeEvent> getBrightnessEvents() {
+        try {
+            ParceledListSlice<BrightnessChangeEvent> events = mDm.getBrightnessEvents();
+            if (events == null) {
+                return Collections.emptyList();
+            }
+            return events.getList();
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set brightness but don't add a BrightnessChangeEvent
+     * STOPSHIP remove when adaptive brightness accepts curves.
+     */
+    public void setBrightness(int brightness) {
+        try {
+            mDm.setBrightness(brightness);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
         @Override
         public void onDisplayEvent(int displayId, int event) {
diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java
index 7cbb436..2411727 100644
--- a/android/hardware/location/ContextHubManager.java
+++ b/android/hardware/location/ContextHubManager.java
@@ -271,6 +271,59 @@
         throw new UnsupportedOperationException("TODO: Implement this");
     }
 
+    /*
+     * Helper function to generate a stub for a non-query transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     *
+     * @return the callback
+     *
+     * @hide
+     */
+    private IContextHubTransactionCallback createTransactionCallback(
+            ContextHubTransaction<Void> transaction) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                Log.e(TAG, "Received a query callback on a non-query request");
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(
+                        ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+            }
+        };
+    }
+
+   /*
+    * Helper function to generate a stub for a query transaction callback.
+    *
+    * @param transaction the transaction to unblock when complete
+    *
+    * @return the callback
+    *
+    * @hide
+    */
+    private IContextHubTransactionCallback createQueryCallback(
+            ContextHubTransaction<List<NanoAppState>> transaction) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        result, nanoappList));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                Log.e(TAG, "Received a non-query callback on a query request");
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+        };
+    }
+
     /**
      * Loads a nanoapp at the specified Context Hub.
      *
@@ -411,7 +464,7 @@
      *
      * @param callback the notification callback to register
      * @param hubInfo the hub to attach this client to
-     * @param handler the handler to invoke the callback, if null uses the current thread Looper
+     * @param handler the handler to invoke the callback, if null uses the main thread's Looper
      *
      * @return the registered client object
      *
diff --git a/android/hardware/location/ContextHubTransaction.java b/android/hardware/location/ContextHubTransaction.java
index 4877d38..a8569ef 100644
--- a/android/hardware/location/ContextHubTransaction.java
+++ b/android/hardware/location/ContextHubTransaction.java
@@ -16,11 +16,16 @@
 package android.hardware.location;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A class describing a request sent to the Context Hub Service.
@@ -29,17 +34,15 @@
  * through the ContextHubManager APIs. The caller can either retrieve the result
  * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
  * asynchronously through a user-defined callback
- * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}).
- *
- * A transaction can be invalidated if the caller of the transaction is no longer active
- * and the reference to this object is lost, or if timeout period has passed in
- * {@link #waitForResponse(long, TimeUnit)}.
+ * ({@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}).
  *
  * @param <T> the type of the contents in the transaction response
  *
  * @hide
  */
 public class ContextHubTransaction<T> {
+    private static final String TAG = "ContextHubTransaction";
+
     /**
      * Constants describing the type of a transaction through the Context Hub Service.
      */
@@ -68,7 +71,8 @@
             TRANSACTION_FAILED_UNINITIALIZED,
             TRANSACTION_FAILED_PENDING,
             TRANSACTION_FAILED_AT_HUB,
-            TRANSACTION_FAILED_TIMEOUT})
+            TRANSACTION_FAILED_TIMEOUT,
+            TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE})
     public @interface Result {}
     public static final int TRANSACTION_SUCCESS = 0;
     /**
@@ -95,6 +99,10 @@
      * Failure mode when the transaction has timed out.
      */
     public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+    /**
+     * Failure mode when the transaction has failed internally at the service.
+     */
+    public static final int TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE = 7;
 
     /**
      * A class describing the response for a ContextHubTransaction.
@@ -146,11 +154,6 @@
     }
 
     /*
-     * The unique identifier representing the transaction.
-     */
-    private int mTransactionId;
-
-    /*
      * The type of the transaction.
      */
     @Type
@@ -171,8 +174,17 @@
      */
     private ContextHubTransaction.Callback<T> mCallback = null;
 
-    ContextHubTransaction(int id, @Type int type) {
-        mTransactionId = id;
+    /*
+     * Synchronization latch used to block on response.
+     */
+    private final CountDownLatch mDoneSignal = new CountDownLatch(1);
+
+    /*
+     * true if the response has been set throught setResponse, false otherwise.
+     */
+    private boolean mIsResponseSet = false;
+
+    ContextHubTransaction(@Type int type) {
         mTransactionType = type;
     }
 
@@ -191,17 +203,26 @@
      * for the transaction represented by this object by the Context Hub, or a
      * specified timeout period has elapsed.
      *
-     * If the specified timeout has passed, the transaction represented by this object
-     * is invalidated by the Context Hub Service (resulting in a timeout failure in the
-     * response).
+     * If the specified timeout has passed, a TimeoutException will be thrown and the caller may
+     * retry the invocation of this method at a later time.
      *
      * @param timeout the timeout duration
      * @param unit the unit of the timeout
      *
      * @return the transaction response
+     *
+     * @throws InterruptedException if the current thread is interrupted while waiting for response
+     * @throws TimeoutException if the timeout period has passed
      */
-    public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) {
-        throw new UnsupportedOperationException("TODO: Implement this");
+    public ContextHubTransaction.Response<T> waitForResponse(
+            long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+        boolean success = mDoneSignal.await(timeout, unit);
+
+        if (!success) {
+            throw new TimeoutException("Timed out while waiting for transaction");
+        }
+
+        return mResponse;
     }
 
     /**
@@ -215,15 +236,100 @@
      * will be immediately posted by the handler. If the transaction has been invalidated,
      * the callback will never be invoked.
      *
+     * A transaction can be invalidated if the process owning the transaction is no longer active
+     * and the reference to this object is lost.
+     *
+     * This method or {@link #setCallbackOnCompletecan(ContextHubTransaction.Callback)} can only be
+     * invoked once, or an IllegalStateException will be thrown.
+     *
      * @param callback the callback to be invoked upon completion
      * @param handler the handler to post the callback
+     *
+     * @throws IllegalStateException if this method is called multiple times
+     * @throws NullPointerException if the callback or handler is null
      */
-    public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
-        throw new UnsupportedOperationException("TODO: Implement this");
+    public void setCallbackOnComplete(
+            @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
+        synchronized (this) {
+            if (callback == null) {
+                throw new NullPointerException("Callback cannot be null");
+            }
+            if (handler == null) {
+                throw new NullPointerException("Handler cannot be null");
+            }
+            if (mCallback != null) {
+                throw new IllegalStateException(
+                        "Cannot set ContextHubTransaction callback multiple times");
+            }
+
+            mCallback = callback;
+            mHandler = handler;
+
+            if (mDoneSignal.getCount() == 0) {
+                boolean callbackPosted = mHandler.post(() -> {
+                    mCallback.onComplete(this, mResponse);
+                });
+
+                if (!callbackPosted) {
+                    Log.e(TAG, "Failed to post callback to Handler");
+                }
+            }
+        }
     }
 
-    private void setResponse(ContextHubTransaction.Response<T> response) {
-        mResponse = response;
-        throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+    /**
+     * Sets a callback to be invoked when the transaction completes.
+     *
+     * Equivalent to {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+     * with the handler being that of the main thread's Looper.
+     *
+     * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+     * can only be invoked once, or an IllegalStateException will be thrown.
+     *
+     * @param callback the callback to be invoked upon completion
+     *
+     * @throws IllegalStateException if this method is called multiple times
+     * @throws NullPointerException if the callback is null
+     */
+    public void setCallbackOnComplete(@NonNull ContextHubTransaction.Callback<T> callback) {
+        setCallbackOnComplete(callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+     * Sets the response of the transaction.
+     *
+     * This method should only be invoked by ContextHubManager as a result of a callback from
+     * the Context Hub Service indicating the response from a transaction. This method should not be
+     * invoked more than once.
+     *
+     * @param response the response to set
+     *
+     * @throws IllegalStateException if this method is invoked multiple times
+     * @throws NullPointerException if the response is null
+     */
+    void setResponse(ContextHubTransaction.Response<T> response) {
+        synchronized (this) {
+            if (response == null) {
+                throw new NullPointerException("Response cannot be null");
+            }
+            if (mIsResponseSet) {
+                throw new IllegalStateException(
+                        "Cannot set response of ContextHubTransaction multiple times");
+            }
+
+            mResponse = response;
+            mIsResponseSet = true;
+
+            mDoneSignal.countDown();
+            if (mCallback != null) {
+                boolean callbackPosted = mHandler.post(() -> {
+                    mCallback.onComplete(this, mResponse);
+                });
+
+                if (!callbackPosted) {
+                    Log.e(TAG, "Failed to post callback to Handler");
+                }
+            }
+        }
     }
 }
diff --git a/android/hardware/location/NanoAppBinary.java b/android/hardware/location/NanoAppBinary.java
index 5454227..934e9e4 100644
--- a/android/hardware/location/NanoAppBinary.java
+++ b/android/hardware/location/NanoAppBinary.java
@@ -22,6 +22,7 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Arrays;
 
 /**
  * @hide
@@ -57,7 +58,7 @@
     private static final int EXPECTED_HEADER_VERSION = 1;
 
     /*
-     * The magic value expected in the header.
+     * The magic value expected in the header as defined in context_hub.h.
      */
     private static final int EXPECTED_MAGIC_VALUE =
             (((int) 'N' <<  0) | ((int) 'A' <<  8) | ((int) 'N' << 16) | ((int) 'O' << 24));
@@ -67,6 +68,17 @@
      */
     private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
 
+    /*
+     * The size of the header in bytes as defined in context_hub.h.
+     */
+    private static final int HEADER_SIZE_BYTES = 40;
+
+    /*
+     * The bit fields for mFlags as defined in context_hub.h.
+     */
+    private static final int NANOAPP_SIGNED_FLAG_BIT = 0x1;
+    private static final int NANOAPP_ENCRYPTED_FLAG_BIT = 0x2;
+
     public NanoAppBinary(byte[] appBinary) {
         mNanoAppBinary = appBinary;
         parseBinaryHeader();
@@ -111,11 +123,26 @@
     /**
      * @return the app binary byte array
      */
-    public byte[] getNanoAppBinary() {
+    public byte[] getBinary() {
         return mNanoAppBinary;
     }
 
     /**
+     * @return the app binary byte array without the leading header
+     *
+     * @throws IndexOutOfBoundsException if the nanoapp binary size is smaller than the header size
+     * @throws NullPointerException if the nanoapp binary is null
+     */
+    public byte[] getBinaryNoHeader() {
+        if (mNanoAppBinary.length < HEADER_SIZE_BYTES) {
+            throw new IndexOutOfBoundsException("NanoAppBinary binary byte size ("
+                + mNanoAppBinary.length + ") is less than header size (" + HEADER_SIZE_BYTES + ")");
+        }
+
+        return Arrays.copyOfRange(mNanoAppBinary, HEADER_SIZE_BYTES, mNanoAppBinary.length);
+    }
+
+    /**
      * @return {@code true} if the header is valid, {@code false} otherwise
      */
     public boolean hasValidHeader() {
@@ -164,6 +191,31 @@
         return mTargetChreApiMinorVersion;
     }
 
+    /**
+     * Returns the flags for the nanoapp as defined in context_hub.h.
+     *
+     * This method is meant to be used by the Context Hub Service.
+     *
+     * @return the flags for the nanoapp
+     */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * @return {@code true} if the nanoapp binary is signed, {@code false} otherwise
+     */
+    public boolean isSigned() {
+        return (mFlags & NANOAPP_SIGNED_FLAG_BIT) != 0;
+    }
+
+    /**
+     * @return {@code true} if the nanoapp binary is encrypted, {@code false} otherwise
+     */
+    public boolean isEncrypted() {
+        return (mFlags & NANOAPP_ENCRYPTED_FLAG_BIT) != 0;
+    }
+
     private NanoAppBinary(Parcel in) {
         int binaryLength = in.readInt();
         mNanoAppBinary = new byte[binaryLength];
diff --git a/android/hardware/sidekick/SidekickInternal.java b/android/hardware/sidekick/SidekickInternal.java
new file mode 100644
index 0000000..fe3550b
--- /dev/null
+++ b/android/hardware/sidekick/SidekickInternal.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.hardware.sidekick;
+
+
+/**
+ * Sidekick local system service interface.
+ *
+ * @hide Only for use within the system server, and maybe by Clockwork Home.
+ */
+public abstract class SidekickInternal {
+
+    /**
+     * Tell Sidekick to reset back to newly-powered-on state.
+     *
+     * @return true on success (Sidekick is reset), false if Sidekick is not
+     * available (failed or not present). Either way, upon return Sidekick is
+     * guaranteed not to be controlling the display.
+     */
+    public abstract boolean reset();
+
+    /**
+     * Tell Sidekick it can start controlling the display.
+     *
+     * SidekickServer may choose not to actually control the display, if it's been told
+     * via other channels to leave the previous image on the display (same as SUSPEND in
+     * a non-Sidekick system).
+     *
+     * @param displayState - one of Display.STATE_DOZE_SUSPEND, Display.STATE_ON_SUSPEND
+     * @return true on success, false on failure (no sidekick available)
+     */
+    public abstract boolean startDisplayControl(int displayState);
+
+    /**
+     * Tell Sidekick it must stop controlling the display.
+     *
+     * No return code because this must always succeed - after return, Sidekick
+     * is guaranteed to not be controlling the display.
+     */
+    public abstract void endDisplayControl();
+
+}
diff --git a/android/hardware/usb/AccessoryFilter.java b/android/hardware/usb/AccessoryFilter.java
new file mode 100644
index 0000000..d9b7c5b
--- /dev/null
+++ b/android/hardware/usb/AccessoryFilter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB accessory.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class AccessoryFilter {
+    // USB accessory manufacturer (or null for unspecified)
+    public final String mManufacturer;
+    // USB accessory model (or null for unspecified)
+    public final String mModel;
+    // USB accessory version (or null for unspecified)
+    public final String mVersion;
+
+    public AccessoryFilter(String manufacturer, String model, String version) {
+        mManufacturer = manufacturer;
+        mModel = model;
+        mVersion = version;
+    }
+
+    public AccessoryFilter(UsbAccessory accessory) {
+        mManufacturer = accessory.getManufacturer();
+        mModel = accessory.getModel();
+        mVersion = accessory.getVersion();
+    }
+
+    public static AccessoryFilter read(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String manufacturer = null;
+        String model = null;
+        String version = null;
+
+        int count = parser.getAttributeCount();
+        for (int i = 0; i < count; i++) {
+            String name = parser.getAttributeName(i);
+            String value = parser.getAttributeValue(i);
+
+            if ("manufacturer".equals(name)) {
+                manufacturer = value;
+            } else if ("model".equals(name)) {
+                model = value;
+            } else if ("version".equals(name)) {
+                version = value;
+            }
+        }
+        return new AccessoryFilter(manufacturer, model, version);
+    }
+
+    public void write(XmlSerializer serializer)throws IOException {
+        serializer.startTag(null, "usb-accessory");
+        if (mManufacturer != null) {
+            serializer.attribute(null, "manufacturer", mManufacturer);
+        }
+        if (mModel != null) {
+            serializer.attribute(null, "model", mModel);
+        }
+        if (mVersion != null) {
+            serializer.attribute(null, "version", mVersion);
+        }
+        serializer.endTag(null, "usb-accessory");
+    }
+
+    public boolean matches(UsbAccessory acc) {
+        if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
+        if (mModel != null && !acc.getModel().equals(mModel)) return false;
+        return !(mVersion != null && !acc.getVersion().equals(mVersion));
+    }
+
+    /**
+     * Is the accessories described {@code accessory} covered by this filter?
+     *
+     * @param accessory A filter describing the accessory
+     *
+     * @return {@code true} iff this the filter covers the accessory
+     */
+    public boolean contains(AccessoryFilter accessory) {
+        if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) {
+            return false;
+        }
+        if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false;
+        return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion));
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        // can't compare if we have wildcard strings
+        if (mManufacturer == null || mModel == null || mVersion == null) {
+            return false;
+        }
+        if (obj instanceof AccessoryFilter) {
+            AccessoryFilter filter = (AccessoryFilter)obj;
+            return (mManufacturer.equals(filter.mManufacturer) &&
+                    mModel.equals(filter.mModel) &&
+                    mVersion.equals(filter.mVersion));
+        }
+        if (obj instanceof UsbAccessory) {
+            UsbAccessory accessory = (UsbAccessory)obj;
+            return (mManufacturer.equals(accessory.getManufacturer()) &&
+                    mModel.equals(accessory.getModel()) &&
+                    mVersion.equals(accessory.getVersion()));
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
+                (mModel == null ? 0 : mModel.hashCode()) ^
+                (mVersion == null ? 0 : mVersion.hashCode()));
+    }
+
+    @Override
+    public String toString() {
+        return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
+                "\", mModel=\"" + mModel +
+                "\", mVersion=\"" + mVersion + "\"]";
+    }
+}
diff --git a/android/hardware/usb/DeviceFilter.java b/android/hardware/usb/DeviceFilter.java
new file mode 100644
index 0000000..439c629
--- /dev/null
+++ b/android/hardware/usb/DeviceFilter.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import android.util.Slog;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB device.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class DeviceFilter {
+    private static final String TAG = DeviceFilter.class.getSimpleName();
+
+    // USB Vendor ID (or -1 for unspecified)
+    public final int mVendorId;
+    // USB Product ID (or -1 for unspecified)
+    public final int mProductId;
+    // USB device or interface class (or -1 for unspecified)
+    public final int mClass;
+    // USB device subclass (or -1 for unspecified)
+    public final int mSubclass;
+    // USB device protocol (or -1 for unspecified)
+    public final int mProtocol;
+    // USB device manufacturer name string (or null for unspecified)
+    public final String mManufacturerName;
+    // USB device product name string (or null for unspecified)
+    public final String mProductName;
+    // USB device serial number string (or null for unspecified)
+    public final String mSerialNumber;
+
+    public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+            String manufacturer, String product, String serialnum) {
+        mVendorId = vid;
+        mProductId = pid;
+        mClass = clasz;
+        mSubclass = subclass;
+        mProtocol = protocol;
+        mManufacturerName = manufacturer;
+        mProductName = product;
+        mSerialNumber = serialnum;
+    }
+
+    public DeviceFilter(UsbDevice device) {
+        mVendorId = device.getVendorId();
+        mProductId = device.getProductId();
+        mClass = device.getDeviceClass();
+        mSubclass = device.getDeviceSubclass();
+        mProtocol = device.getDeviceProtocol();
+        mManufacturerName = device.getManufacturerName();
+        mProductName = device.getProductName();
+        mSerialNumber = device.getSerialNumber();
+    }
+
+    public static DeviceFilter read(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int vendorId = -1;
+        int productId = -1;
+        int deviceClass = -1;
+        int deviceSubclass = -1;
+        int deviceProtocol = -1;
+        String manufacturerName = null;
+        String productName = null;
+        String serialNumber = null;
+
+        int count = parser.getAttributeCount();
+        for (int i = 0; i < count; i++) {
+            String name = parser.getAttributeName(i);
+            String value = parser.getAttributeValue(i);
+            // Attribute values are ints or strings
+            if ("manufacturer-name".equals(name)) {
+                manufacturerName = value;
+            } else if ("product-name".equals(name)) {
+                productName = value;
+            } else if ("serial-number".equals(name)) {
+                serialNumber = value;
+            } else {
+                int intValue;
+                int radix = 10;
+                if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
+                        (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+                    // allow hex values starting with 0x or 0X
+                    radix = 16;
+                    value = value.substring(2);
+                }
+                try {
+                    intValue = Integer.parseInt(value, radix);
+                } catch (NumberFormatException e) {
+                    Slog.e(TAG, "invalid number for field " + name, e);
+                    continue;
+                }
+                if ("vendor-id".equals(name)) {
+                    vendorId = intValue;
+                } else if ("product-id".equals(name)) {
+                    productId = intValue;
+                } else if ("class".equals(name)) {
+                    deviceClass = intValue;
+                } else if ("subclass".equals(name)) {
+                    deviceSubclass = intValue;
+                } else if ("protocol".equals(name)) {
+                    deviceProtocol = intValue;
+                }
+            }
+        }
+        return new DeviceFilter(vendorId, productId,
+                deviceClass, deviceSubclass, deviceProtocol,
+                manufacturerName, productName, serialNumber);
+    }
+
+    public void write(XmlSerializer serializer) throws IOException {
+        serializer.startTag(null, "usb-device");
+        if (mVendorId != -1) {
+            serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
+        }
+        if (mProductId != -1) {
+            serializer.attribute(null, "product-id", Integer.toString(mProductId));
+        }
+        if (mClass != -1) {
+            serializer.attribute(null, "class", Integer.toString(mClass));
+        }
+        if (mSubclass != -1) {
+            serializer.attribute(null, "subclass", Integer.toString(mSubclass));
+        }
+        if (mProtocol != -1) {
+            serializer.attribute(null, "protocol", Integer.toString(mProtocol));
+        }
+        if (mManufacturerName != null) {
+            serializer.attribute(null, "manufacturer-name", mManufacturerName);
+        }
+        if (mProductName != null) {
+            serializer.attribute(null, "product-name", mProductName);
+        }
+        if (mSerialNumber != null) {
+            serializer.attribute(null, "serial-number", mSerialNumber);
+        }
+        serializer.endTag(null, "usb-device");
+    }
+
+    private boolean matches(int clasz, int subclass, int protocol) {
+        return ((mClass == -1 || clasz == mClass) &&
+                (mSubclass == -1 || subclass == mSubclass) &&
+                (mProtocol == -1 || protocol == mProtocol));
+    }
+
+    public boolean matches(UsbDevice device) {
+        if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
+        if (mProductId != -1 && device.getProductId() != mProductId) return false;
+        if (mManufacturerName != null && device.getManufacturerName() == null) return false;
+        if (mProductName != null && device.getProductName() == null) return false;
+        if (mSerialNumber != null && device.getSerialNumber() == null) return false;
+        if (mManufacturerName != null && device.getManufacturerName() != null &&
+                !mManufacturerName.equals(device.getManufacturerName())) return false;
+        if (mProductName != null && device.getProductName() != null &&
+                !mProductName.equals(device.getProductName())) return false;
+        if (mSerialNumber != null && device.getSerialNumber() != null &&
+                !mSerialNumber.equals(device.getSerialNumber())) return false;
+
+        // check device class/subclass/protocol
+        if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+                device.getDeviceProtocol())) return true;
+
+        // if device doesn't match, check the interfaces
+        int count = device.getInterfaceCount();
+        for (int i = 0; i < count; i++) {
+            UsbInterface intf = device.getInterface(i);
+            if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+                    intf.getInterfaceProtocol())) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * If the device described by {@code device} covered by this filter?
+     *
+     * @param device The device
+     *
+     * @return {@code true} iff this filter covers the {@code device}
+     */
+    public boolean contains(DeviceFilter device) {
+        // -1 and null means "match anything"
+
+        if (mVendorId != -1 && device.mVendorId != mVendorId) return false;
+        if (mProductId != -1 && device.mProductId != mProductId) return false;
+        if (mManufacturerName != null && !Objects.equals(mManufacturerName,
+                device.mManufacturerName)) {
+            return false;
+        }
+        if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) {
+            return false;
+        }
+        if (mSerialNumber != null
+                && !Objects.equals(mSerialNumber, device.mSerialNumber)) {
+            return false;
+        }
+
+        // check device class/subclass/protocol
+        return matches(device.mClass, device.mSubclass, device.mProtocol);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        // can't compare if we have wildcard strings
+        if (mVendorId == -1 || mProductId == -1 ||
+                mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+            return false;
+        }
+        if (obj instanceof DeviceFilter) {
+            DeviceFilter filter = (DeviceFilter)obj;
+
+            if (filter.mVendorId != mVendorId ||
+                    filter.mProductId != mProductId ||
+                    filter.mClass != mClass ||
+                    filter.mSubclass != mSubclass ||
+                    filter.mProtocol != mProtocol) {
+                return(false);
+            }
+            if ((filter.mManufacturerName != null &&
+                    mManufacturerName == null) ||
+                    (filter.mManufacturerName == null &&
+                            mManufacturerName != null) ||
+                    (filter.mProductName != null &&
+                            mProductName == null)  ||
+                    (filter.mProductName == null &&
+                            mProductName != null) ||
+                    (filter.mSerialNumber != null &&
+                            mSerialNumber == null)  ||
+                    (filter.mSerialNumber == null &&
+                            mSerialNumber != null)) {
+                return(false);
+            }
+            if  ((filter.mManufacturerName != null &&
+                    mManufacturerName != null &&
+                    !mManufacturerName.equals(filter.mManufacturerName)) ||
+                    (filter.mProductName != null &&
+                            mProductName != null &&
+                            !mProductName.equals(filter.mProductName)) ||
+                    (filter.mSerialNumber != null &&
+                            mSerialNumber != null &&
+                            !mSerialNumber.equals(filter.mSerialNumber))) {
+                return false;
+            }
+            return true;
+        }
+        if (obj instanceof UsbDevice) {
+            UsbDevice device = (UsbDevice)obj;
+            if (device.getVendorId() != mVendorId ||
+                    device.getProductId() != mProductId ||
+                    device.getDeviceClass() != mClass ||
+                    device.getDeviceSubclass() != mSubclass ||
+                    device.getDeviceProtocol() != mProtocol) {
+                return(false);
+            }
+            if ((mManufacturerName != null && device.getManufacturerName() == null) ||
+                    (mManufacturerName == null && device.getManufacturerName() != null) ||
+                    (mProductName != null && device.getProductName() == null) ||
+                    (mProductName == null && device.getProductName() != null) ||
+                    (mSerialNumber != null && device.getSerialNumber() == null) ||
+                    (mSerialNumber == null && device.getSerialNumber() != null)) {
+                return(false);
+            }
+            if ((device.getManufacturerName() != null &&
+                    !mManufacturerName.equals(device.getManufacturerName())) ||
+                    (device.getProductName() != null &&
+                            !mProductName.equals(device.getProductName())) ||
+                    (device.getSerialNumber() != null &&
+                            !mSerialNumber.equals(device.getSerialNumber()))) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return (((mVendorId << 16) | mProductId) ^
+                ((mClass << 16) | (mSubclass << 8) | mProtocol));
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
+                ",mClass=" + mClass + ",mSubclass=" + mSubclass +
+                ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
+                ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
+                "]";
+    }
+}
diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java
index 996824d..6ce9669 100644
--- a/android/hardware/usb/UsbManager.java
+++ b/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@
             "android.hardware.usb.action.USB_PORT_CHANGED";
 
    /**
-     * Activity intent sent when a USB device is attached.
+     * Activity intent sent when user attaches a USB device.
      *
      * This intent is sent when a USB device is attached to the USB bus when in host mode.
      * <ul>
@@ -128,7 +128,7 @@
             "android.hardware.usb.action.USB_DEVICE_DETACHED";
 
    /**
-     * Activity intent sent when a USB accessory is attached.
+     * Activity intent sent when user attaches a USB accessory.
      *
      * <ul>
      * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
diff --git a/android/location/Location.java b/android/location/Location.java
index e7f903e..c9d2f7f 100644
--- a/android/location/Location.java
+++ b/android/location/Location.java
@@ -821,7 +821,7 @@
      * considered 1 standard deviation.
      *
      * <p>For example, if {@link #getAltitude()} returns 150, and
-     * {@link #getVerticalAccuracyMeters()} ()} returns 20 then there is a 68% probability
+     * {@link #getVerticalAccuracyMeters()} returns 20 then there is a 68% probability
      * of the true altitude being between 130 and 170 meters.
      *
      * <p>If this location does not have a vertical accuracy, then 0.0 is returned.
@@ -933,7 +933,7 @@
      * considered 1 standard deviation.
      *
      * <p>For example, if {@link #getBearing()} returns 60, and
-     * {@link #getBearingAccuracyDegrees()} ()} returns 10, then there is a 68% probability of the
+     * {@link #getBearingAccuracyDegrees()} returns 10, then there is a 68% probability of the
      * true bearing being between 50 and 70 degrees.
      *
      * <p>If this location does not have a bearing accuracy, then 0.0 is returned.
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 20405d3..7afe267 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -244,6 +244,7 @@
         SUPPRESSIBLE_USAGES.put(USAGE_GAME,                              SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
         SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING,    SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT,                         SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN,                           SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
     }
 
     /**
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index dab7632..58976ca 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -43,17 +43,16 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.Pair;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -1966,9 +1965,28 @@
      */
     private boolean querySoundEffectsEnabled(int user) {
         return Settings.System.getIntForUser(getContext().getContentResolver(),
-                Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
+                Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0
+                && !areSystemSoundsZenModeBlocked(getContext());
     }
 
+    private boolean areSystemSoundsZenModeBlocked(Context context) {
+        int zenMode = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.ZEN_MODE, 0);
+
+        switch (zenMode) {
+            case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+            case Settings.Global.ZEN_MODE_ALARMS:
+                return true;
+            case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                final NotificationManager noMan = (NotificationManager) context
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
+                return (noMan.getNotificationPolicy().priorityCategories
+                        & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+            case Settings.Global.ZEN_MODE_OFF:
+            default:
+                return false;
+        }
+    }
 
     /**
      *  Load Sound effects.
diff --git a/android/media/BufferingParams.java b/android/media/BufferingParams.java
index 681271b..521e897 100644
--- a/android/media/BufferingParams.java
+++ b/android/media/BufferingParams.java
@@ -26,170 +26,68 @@
 /**
  * Structure for source buffering management params.
  *
- * Used by {@link MediaPlayer#getDefaultBufferingParams()},
- * {@link MediaPlayer#getBufferingParams()} and
+ * Used by {@link MediaPlayer#getBufferingParams()} and
  * {@link MediaPlayer#setBufferingParams(BufferingParams)}
  * to control source buffering behavior.
  *
  * <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
  * (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
- * is playing back source). {@link BufferingParams} includes mode and corresponding
- * watermarks for each stage of source buffering. The watermarks could be either size
- * based (in milliseconds), or time based (in kilobytes) or both, depending on the mode.
+ * is playing back source). {@link BufferingParams} includes corresponding marks for each
+ * stage of source buffering. The marks are time based (in milliseconds).
  *
- * <p>There are 4 buffering modes: {@link #BUFFERING_MODE_NONE},
- * {@link #BUFFERING_MODE_TIME_ONLY}, {@link #BUFFERING_MODE_SIZE_ONLY} and
- * {@link #BUFFERING_MODE_TIME_THEN_SIZE}.
- * {@link MediaPlayer} source component has default buffering modes which can be queried
- * by calling {@link MediaPlayer#getDefaultBufferingParams()}.
- * Users should always use those default modes or their downsized version when trying to
- * change buffering params. For example, {@link #BUFFERING_MODE_TIME_THEN_SIZE} can be
- * downsized to {@link #BUFFERING_MODE_NONE}, {@link #BUFFERING_MODE_TIME_ONLY} or
- * {@link #BUFFERING_MODE_SIZE_ONLY}. But {@link #BUFFERING_MODE_TIME_ONLY} can not be
- * downsized to {@link #BUFFERING_MODE_SIZE_ONLY}.
+ * <p>{@link MediaPlayer} source component has default marks which can be queried by
+ * calling {@link MediaPlayer#getBufferingParams()} before any change is made by
+ * {@link MediaPlayer#setBufferingParams()}.
  * <ul>
- * <li><strong>initial buffering stage:</strong> has one watermark which is used when
- * {@link MediaPlayer} is being prepared. When cached data amount exceeds this watermark,
- * {@link MediaPlayer} is prepared.</li>
- * <li><strong>rebuffering stage:</strong> has two watermarks, low and high, which are
- * used when {@link MediaPlayer} is playing back content.
+ * <li><strong>initial buffering:</strong> initialMarkMs is used when
+ * {@link MediaPlayer} is being prepared. When cached data amount exceeds this mark
+ * {@link MediaPlayer} is prepared. </li>
+ * <li><strong>rebuffering during playback:</strong> resumePlaybackMarkMs is used when
+ * {@link MediaPlayer} is playing back content.
  * <ul>
- * <li> When cached data amount exceeds high watermark, {@link MediaPlayer} will pause
- * buffering. Buffering will resume when cache runs below some limit which could be low
- * watermark or some intermediate value decided by the source component.</li>
- * <li> When cached data amount runs below low watermark, {@link MediaPlayer} will paused
- * playback. Playback will resume when cached data amount exceeds high watermark
- * or reaches end of stream.</li>
- * </ul>
+ * <li> {@link MediaPlayer} has internal mark, namely pausePlaybackMarkMs, to decide when
+ * to pause playback if cached data amount runs low. This internal mark varies based on
+ * type of data source. </li>
+ * <li> When cached data amount exceeds resumePlaybackMarkMs, {@link MediaPlayer} will
+ * resume playback if it has been paused due to low cached data amount. The internal mark
+ * pausePlaybackMarkMs shall be less than resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely pauseRebufferingMarkMs, to decide
+ * when to pause rebuffering. Apparently, this internal mark shall be no less than
+ * resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely resumeRebufferingMarkMs, to decide
+ * when to resume buffering. This internal mark varies based on type of data source. This
+ * mark shall be larger than pausePlaybackMarkMs, and less than pauseRebufferingMarkMs.
+ * </li>
+ * </ul> </li>
  * </ul>
  * <p>Users should use {@link Builder} to change {@link BufferingParams}.
  * @hide
  */
 public final class BufferingParams implements Parcelable {
-    /**
-     * This mode indicates that source buffering is not supported.
-     */
-    public static final int BUFFERING_MODE_NONE = 0;
-    /**
-     * This mode indicates that only time based source buffering is supported. This means
-     * the watermark(s) are time based.
-     */
-    public static final int BUFFERING_MODE_TIME_ONLY = 1;
-    /**
-     * This mode indicates that only size based source buffering is supported. This means
-     * the watermark(s) are size based.
-     */
-    public static final int BUFFERING_MODE_SIZE_ONLY = 2;
-    /**
-     * This mode indicates that both time and size based source buffering are supported,
-     * and time based calculation precedes size based. Size based calculation will be used
-     * only when time information is not available from the source.
-     */
-    public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3;
-
-    /** @hide */
-    @IntDef(
-        value = {
-                BUFFERING_MODE_NONE,
-                BUFFERING_MODE_TIME_ONLY,
-                BUFFERING_MODE_SIZE_ONLY,
-                BUFFERING_MODE_TIME_THEN_SIZE,
-        }
-    )
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface BufferingMode {}
-
-    private static final int BUFFERING_NO_WATERMARK = -1;
+    private static final int BUFFERING_NO_MARK = -1;
 
     // params
-    private int mInitialBufferingMode = BUFFERING_MODE_NONE;
-    private int mRebufferingMode = BUFFERING_MODE_NONE;
+    private int mInitialMarkMs = BUFFERING_NO_MARK;
 
-    private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
-    private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
-
-    private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
-    private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
-    private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
-    private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+    private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
 
     private BufferingParams() {
     }
 
     /**
-     * Return the initial buffering mode used when {@link MediaPlayer} is being prepared.
-     * @return one of the values that can be set in {@link Builder#setInitialBufferingMode(int)}
+     * Return initial buffering mark in milliseconds.
+     * @return initial buffering mark in milliseconds
      */
-    public int getInitialBufferingMode() {
-        return mInitialBufferingMode;
+    public int getInitialMarkMs() {
+        return mInitialMarkMs;
     }
 
     /**
-     * Return the rebuffering mode used when {@link MediaPlayer} is playing back source.
-     * @return one of the values that can be set in {@link Builder#setRebufferingMode(int)}
+     * Return the mark in milliseconds for resuming playback.
+     * @return the mark for resuming playback in milliseconds
      */
-    public int getRebufferingMode() {
-        return mRebufferingMode;
-    }
-
-    /**
-     * Return the time based initial buffering watermark in milliseconds.
-     * It is meaningful only when initial buffering mode obatined from
-     * {@link #getInitialBufferingMode()} is time based.
-     * @return time based initial buffering watermark in milliseconds
-     */
-    public int getInitialBufferingWatermarkMs() {
-        return mInitialWatermarkMs;
-    }
-
-    /**
-     * Return the size based initial buffering watermark in kilobytes.
-     * It is meaningful only when initial buffering mode obatined from
-     * {@link #getInitialBufferingMode()} is size based.
-     * @return size based initial buffering watermark in kilobytes
-     */
-    public int getInitialBufferingWatermarkKB() {
-        return mInitialWatermarkKB;
-    }
-
-    /**
-     * Return the time based low watermark in milliseconds for rebuffering.
-     * It is meaningful only when rebuffering mode obatined from
-     * {@link #getRebufferingMode()} is time based.
-     * @return time based low watermark for rebuffering in milliseconds
-     */
-    public int getRebufferingWatermarkLowMs() {
-        return mRebufferingWatermarkLowMs;
-    }
-
-    /**
-     * Return the time based high watermark in milliseconds for rebuffering.
-     * It is meaningful only when rebuffering mode obatined from
-     * {@link #getRebufferingMode()} is time based.
-     * @return time based high watermark for rebuffering in milliseconds
-     */
-    public int getRebufferingWatermarkHighMs() {
-        return mRebufferingWatermarkHighMs;
-    }
-
-    /**
-     * Return the size based low watermark in kilobytes for rebuffering.
-     * It is meaningful only when rebuffering mode obatined from
-     * {@link #getRebufferingMode()} is size based.
-     * @return size based low watermark for rebuffering in kilobytes
-     */
-    public int getRebufferingWatermarkLowKB() {
-        return mRebufferingWatermarkLowKB;
-    }
-
-    /**
-     * Return the size based high watermark in kilobytes for rebuffering.
-     * It is meaningful only when rebuffering mode obatined from
-     * {@link #getRebufferingMode()} is size based.
-     * @return size based high watermark for rebuffering in kilobytes
-     */
-    public int getRebufferingWatermarkHighKB() {
-        return mRebufferingWatermarkHighKB;
+    public int getResumePlaybackMarkMs() {
+        return mResumePlaybackMarkMs;
     }
 
     /**
@@ -200,27 +98,19 @@
      * <pre class="prettyprint">
      * BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
      * myParams = new BufferingParams.Builder(myParams)
-     *             .setInitialBufferingWatermarkMs(10000)
-     *             .build();
+     *         .setInitialMarkMs(10000)
+     *         .setResumePlaybackMarkMs(15000)
+     *         .build();
      * mediaplayer.setBufferingParams(myParams);
      * </pre>
      */
     public static class Builder {
-        private int mInitialBufferingMode = BUFFERING_MODE_NONE;
-        private int mRebufferingMode = BUFFERING_MODE_NONE;
-
-        private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
-        private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
-
-        private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
-        private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
-        private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
-        private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+        private int mInitialMarkMs = BUFFERING_NO_MARK;
+        private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
 
         /**
          * Constructs a new Builder with the defaults.
-         * By default, both initial buffering mode and rebuffering mode are
-         * {@link BufferingParams#BUFFERING_MODE_NONE}, and all watermarks are -1.
+         * By default, all marks are -1.
          */
         public Builder() {
         }
@@ -231,16 +121,8 @@
          * in the new Builder.
          */
         public Builder(BufferingParams bp) {
-            mInitialBufferingMode = bp.mInitialBufferingMode;
-            mRebufferingMode = bp.mRebufferingMode;
-
-            mInitialWatermarkMs = bp.mInitialWatermarkMs;
-            mInitialWatermarkKB = bp.mInitialWatermarkKB;
-
-            mRebufferingWatermarkLowMs = bp.mRebufferingWatermarkLowMs;
-            mRebufferingWatermarkHighMs = bp.mRebufferingWatermarkHighMs;
-            mRebufferingWatermarkLowKB = bp.mRebufferingWatermarkLowKB;
-            mRebufferingWatermarkHighKB = bp.mRebufferingWatermarkHighKB;
+            mInitialMarkMs = bp.mInitialMarkMs;
+            mResumePlaybackMarkMs = bp.mResumePlaybackMarkMs;
         }
 
         /**
@@ -250,179 +132,37 @@
          * @return a new {@link BufferingParams} object
          */
         public BufferingParams build() {
-            if (isTimeBasedMode(mRebufferingMode)
-                    && mRebufferingWatermarkLowMs > mRebufferingWatermarkHighMs) {
-                throw new IllegalStateException("Illegal watermark:"
-                        + mRebufferingWatermarkLowMs + " : " + mRebufferingWatermarkHighMs);
-            }
-            if (isSizeBasedMode(mRebufferingMode)
-                    && mRebufferingWatermarkLowKB > mRebufferingWatermarkHighKB) {
-                throw new IllegalStateException("Illegal watermark:"
-                        + mRebufferingWatermarkLowKB + " : " + mRebufferingWatermarkHighKB);
-            }
-
             BufferingParams bp = new BufferingParams();
-            bp.mInitialBufferingMode = mInitialBufferingMode;
-            bp.mRebufferingMode = mRebufferingMode;
+            bp.mInitialMarkMs = mInitialMarkMs;
+            bp.mResumePlaybackMarkMs = mResumePlaybackMarkMs;
 
-            bp.mInitialWatermarkMs = mInitialWatermarkMs;
-            bp.mInitialWatermarkKB = mInitialWatermarkKB;
-
-            bp.mRebufferingWatermarkLowMs = mRebufferingWatermarkLowMs;
-            bp.mRebufferingWatermarkHighMs = mRebufferingWatermarkHighMs;
-            bp.mRebufferingWatermarkLowKB = mRebufferingWatermarkLowKB;
-            bp.mRebufferingWatermarkHighKB = mRebufferingWatermarkHighKB;
             return bp;
         }
 
-        private boolean isTimeBasedMode(int mode) {
-            return (mode == BUFFERING_MODE_TIME_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
-        }
-
-        private boolean isSizeBasedMode(int mode) {
-            return (mode == BUFFERING_MODE_SIZE_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
-        }
-
         /**
-         * Sets the initial buffering mode.
-         * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
-         *     {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
-         *     {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
-         *     {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+         * Sets the time based mark in milliseconds for initial buffering.
+         * @param markMs time based mark in milliseconds
          * @return the same Builder instance.
          */
-        public Builder setInitialBufferingMode(@BufferingMode int mode) {
-            switch (mode) {
-                case BUFFERING_MODE_NONE:
-                case BUFFERING_MODE_TIME_ONLY:
-                case BUFFERING_MODE_SIZE_ONLY:
-                case BUFFERING_MODE_TIME_THEN_SIZE:
-                     mInitialBufferingMode = mode;
-                     break;
-                default:
-                     throw new IllegalArgumentException("Illegal buffering mode " + mode);
-            }
+        public Builder setInitialMarkMs(int markMs) {
+            mInitialMarkMs = markMs;
             return this;
         }
 
         /**
-         * Sets the rebuffering mode.
-         * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
-         *     {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
-         *     {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
-         *     {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+         * Sets the time based mark in milliseconds for resuming playback.
+         * @param markMs time based mark in milliseconds for resuming playback
          * @return the same Builder instance.
          */
-        public Builder setRebufferingMode(@BufferingMode int mode) {
-            switch (mode) {
-                case BUFFERING_MODE_NONE:
-                case BUFFERING_MODE_TIME_ONLY:
-                case BUFFERING_MODE_SIZE_ONLY:
-                case BUFFERING_MODE_TIME_THEN_SIZE:
-                     mRebufferingMode = mode;
-                     break;
-                default:
-                     throw new IllegalArgumentException("Illegal buffering mode " + mode);
-            }
-            return this;
-        }
-
-        /**
-         * Sets the time based watermark in milliseconds for initial buffering.
-         * @param watermarkMs time based watermark in milliseconds
-         * @return the same Builder instance.
-         */
-        public Builder setInitialBufferingWatermarkMs(int watermarkMs) {
-            mInitialWatermarkMs = watermarkMs;
-            return this;
-        }
-
-        /**
-         * Sets the size based watermark in kilobytes for initial buffering.
-         * @param watermarkKB size based watermark in kilobytes
-         * @return the same Builder instance.
-         */
-        public Builder setInitialBufferingWatermarkKB(int watermarkKB) {
-            mInitialWatermarkKB = watermarkKB;
-            return this;
-        }
-
-        /**
-         * Sets the time based low watermark in milliseconds for rebuffering.
-         * @param watermarkMs time based low watermark in milliseconds
-         * @return the same Builder instance.
-         */
-        public Builder setRebufferingWatermarkLowMs(int watermarkMs) {
-            mRebufferingWatermarkLowMs = watermarkMs;
-            return this;
-        }
-
-        /**
-         * Sets the time based high watermark in milliseconds for rebuffering.
-         * @param watermarkMs time based high watermark in milliseconds
-         * @return the same Builder instance.
-         */
-        public Builder setRebufferingWatermarkHighMs(int watermarkMs) {
-            mRebufferingWatermarkHighMs = watermarkMs;
-            return this;
-        }
-
-        /**
-         * Sets the size based low watermark in milliseconds for rebuffering.
-         * @param watermarkKB size based low watermark in milliseconds
-         * @return the same Builder instance.
-         */
-        public Builder setRebufferingWatermarkLowKB(int watermarkKB) {
-            mRebufferingWatermarkLowKB = watermarkKB;
-            return this;
-        }
-
-        /**
-         * Sets the size based high watermark in milliseconds for rebuffering.
-         * @param watermarkKB size based high watermark in milliseconds
-         * @return the same Builder instance.
-         */
-        public Builder setRebufferingWatermarkHighKB(int watermarkKB) {
-            mRebufferingWatermarkHighKB = watermarkKB;
-            return this;
-        }
-
-        /**
-         * Sets the time based low and high watermarks in milliseconds for rebuffering.
-         * @param lowWatermarkMs time based low watermark in milliseconds
-         * @param highWatermarkMs time based high watermark in milliseconds
-         * @return the same Builder instance.
-         */
-        public Builder setRebufferingWatermarksMs(int lowWatermarkMs, int highWatermarkMs) {
-            mRebufferingWatermarkLowMs = lowWatermarkMs;
-            mRebufferingWatermarkHighMs = highWatermarkMs;
-            return this;
-        }
-
-        /**
-         * Sets the size based low and high watermarks in kilobytes for rebuffering.
-         * @param lowWatermarkKB size based low watermark in kilobytes
-         * @param highWatermarkKB size based high watermark in kilobytes
-         * @return the same Builder instance.
-         */
-        public Builder setRebufferingWatermarksKB(int lowWatermarkKB, int highWatermarkKB) {
-            mRebufferingWatermarkLowKB = lowWatermarkKB;
-            mRebufferingWatermarkHighKB = highWatermarkKB;
+        public Builder setResumePlaybackMarkMs(int markMs) {
+            mResumePlaybackMarkMs = markMs;
             return this;
         }
     }
 
     private BufferingParams(Parcel in) {
-        mInitialBufferingMode = in.readInt();
-        mRebufferingMode = in.readInt();
-
-        mInitialWatermarkMs = in.readInt();
-        mInitialWatermarkKB = in.readInt();
-
-        mRebufferingWatermarkLowMs = in.readInt();
-        mRebufferingWatermarkHighMs = in.readInt();
-        mRebufferingWatermarkLowKB = in.readInt();
-        mRebufferingWatermarkHighKB = in.readInt();
+        mInitialMarkMs = in.readInt();
+        mResumePlaybackMarkMs = in.readInt();
     }
 
     public static final Parcelable.Creator<BufferingParams> CREATOR =
@@ -446,15 +186,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mInitialBufferingMode);
-        dest.writeInt(mRebufferingMode);
-
-        dest.writeInt(mInitialWatermarkMs);
-        dest.writeInt(mInitialWatermarkKB);
-
-        dest.writeInt(mRebufferingWatermarkLowMs);
-        dest.writeInt(mRebufferingWatermarkHighMs);
-        dest.writeInt(mRebufferingWatermarkLowKB);
-        dest.writeInt(mRebufferingWatermarkHighKB);
+        dest.writeInt(mInitialMarkMs);
+        dest.writeInt(mResumePlaybackMarkMs);
     }
 }
diff --git a/android/media/ExifInterface.java b/android/media/ExifInterface.java
index ba41a7b..9175416 100644
--- a/android/media/ExifInterface.java
+++ b/android/media/ExifInterface.java
@@ -2564,51 +2564,66 @@
                 });
             }
 
+            String hasImage = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
             String hasVideo = retriever.extractMetadata(
                     MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
 
-            final String METADATA_HAS_VIDEO_VALUE_YES = "yes";
-            if (METADATA_HAS_VIDEO_VALUE_YES.equals(hasVideo)) {
-                String width = retriever.extractMetadata(
+            String width = null;
+            String height = null;
+            String rotation = null;
+            final String METADATA_VALUE_YES = "yes";
+            // If the file has both image and video, prefer image info over video info.
+            // App querying ExifInterface is most likely using the bitmap path which
+            // picks the image first.
+            if (METADATA_VALUE_YES.equals(hasImage)) {
+                width = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
+                height = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
+                rotation = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
+            } else if (METADATA_VALUE_YES.equals(hasVideo)) {
+                width = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-                String height = retriever.extractMetadata(
+                height = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
-
-                if (width != null) {
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
-                            ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
-                }
-
-                if (height != null) {
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
-                            ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
-                }
-
-                String rotation = retriever.extractMetadata(
+                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_90;
-                            break;
-                        case 180:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_180;
-                            break;
-                        case 270:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
-                            break;
-                    }
+            if (width != null) {
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
+                        ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
+            }
 
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
-                            ExifAttribute.createUShort(orientation, mExifByteOrder));
+            if (height != null) {
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
+                        ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
+            }
+
+            if (rotation != null) {
+                int orientation = ExifInterface.ORIENTATION_NORMAL;
+
+                // all rotation angles in CW
+                switch (Integer.parseInt(rotation)) {
+                    case 90:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_90;
+                        break;
+                    case 180:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_180;
+                        break;
+                    case 270:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_270;
+                        break;
                 }
 
-                if (DEBUG) {
-                    Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
-                }
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
+                        ExifAttribute.createUShort(orientation, mExifByteOrder));
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
             }
         } finally {
             retriever.release();
diff --git a/android/media/MediaDrm.java b/android/media/MediaDrm.java
index 88b1c5f..1feea89 100644
--- a/android/media/MediaDrm.java
+++ b/android/media/MediaDrm.java
@@ -994,7 +994,6 @@
      * {@link #PROPERTY_VENDOR}, {@link #PROPERTY_VERSION},
      * {@link #PROPERTY_DESCRIPTION}, {@link #PROPERTY_ALGORITHMS}
      */
-    /* FIXME this throws IllegalStateException for invalid property names */
     @NonNull
     public native String getPropertyString(@NonNull @StringProperty String propertyName);
 
@@ -1002,7 +1001,6 @@
      * Byte array property name: the device unique identifier is established during
      * device provisioning and provides a means of uniquely identifying each device.
      */
-    /* FIXME this throws IllegalStateException for invalid property names */
     public static final String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId";
 
     /** @hide */
diff --git a/android/media/MediaFormat.java b/android/media/MediaFormat.java
index ed5f7d8..c475e12 100644
--- a/android/media/MediaFormat.java
+++ b/android/media/MediaFormat.java
@@ -96,6 +96,19 @@
  * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
  * <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr>
  * </table>
+ *
+ * Image formats have the following keys:
+ * <table>
+ * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
+ * <tr><td>{@link #KEY_WIDTH}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_HEIGHT}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_COLOR_FORMAT}</td><td>Integer</td><td>set by the user
+ *         for encoders, readable in the output format of decoders</b></td></tr>
+ * <tr><td>{@link #KEY_GRID_WIDTH}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_HEIGHT}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_ROWS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_COLS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * </table>
  */
 public final class MediaFormat {
     public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
@@ -126,6 +139,35 @@
     public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
 
     /**
+     * MIME type for HEIF still image data encoded in HEVC.
+     *
+     * To decode such an image, {@link MediaCodec} decoder for
+     * {@ #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form
+     * the correct {@link #MediaFormat} based on additional information in
+     * the track format, and send it to {@link MediaCodec#configure}.
+     *
+     * The track's MediaFormat will come with {@link #KEY_WIDTH} and
+     * {@link #KEY_HEIGHT} keys, which describes the width and height
+     * of the image. If the image doesn't contain grid (i.e. none of
+     * {@link #KEY_GRID_WIDTH}, {@link #KEY_GRID_HEIGHT},
+     * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLS} are present}), the
+     * track will contain a single sample of coded data for the entire image,
+     * and the image width and height should be used to set up the decoder.
+     *
+     * If the image does come with grid, each sample from the track will
+     * contain one tile in the grid, of which the size is described by
+     * {@link #KEY_GRID_WIDTH} and {@link #KEY_GRID_HEIGHT}. This size
+     * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be
+     * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS}
+     * by {@link #KEY_GRID_COLS} samples in row-major, top-row first,
+     * left-to-right order. The output image should be reconstructed by
+     * first tiling the decoding results of the tiles in the correct order,
+     * then trimming (before rotation is applied) on the bottom and right
+     * side, if the tiled area is larger than the image width and height.
+     */
+    public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
+
+    /**
      * MIME type for WebVTT subtitle data.
      */
     public static final String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -232,6 +274,54 @@
     public static final String KEY_FRAME_RATE = "frame-rate";
 
     /**
+     * A key describing the grid width of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+     * track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_HEIGHT
+     * @see #KEY_GRID_ROWS
+     * @see #KEY_GRID_COLS
+     */
+    public static final String KEY_GRID_WIDTH = "grid-width";
+
+    /**
+     * A key describing the grid height of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+     * track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_WIDTH
+     * @see #KEY_GRID_ROWS
+     * @see #KEY_GRID_COLS
+     */
+    public static final String KEY_GRID_HEIGHT = "grid-height";
+
+    /**
+     * A key describing the number of grid rows in the content in a
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_WIDTH
+     * @see #KEY_GRID_HEIGHT
+     * @see #KEY_GRID_COLS
+     */
+    public static final String KEY_GRID_ROWS = "grid-rows";
+
+    /**
+     * A key describing the number of grid columns in the content in a
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_WIDTH
+     * @see #KEY_GRID_HEIGHT
+     * @see #KEY_GRID_ROWS
+     */
+    public static final String KEY_GRID_COLS = "grid-cols";
+
+    /**
      * A key describing the raw audio sample encoding/format.
      *
      * <p>The associated value is an integer, using one of the
diff --git a/android/media/MediaMetadataRetriever.java b/android/media/MediaMetadataRetriever.java
index 760cc49..0b86401 100644
--- a/android/media/MediaMetadataRetriever.java
+++ b/android/media/MediaMetadataRetriever.java
@@ -47,7 +47,7 @@
     // The field below is accessed by native methods
     @SuppressWarnings("unused")
     private long mNativeContext;
- 
+
     private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF;
 
     public MediaMetadataRetriever() {
@@ -58,7 +58,7 @@
      * Sets the data source (file pathname) to use. Call this
      * method before the rest of the methods in this class. This method may be
      * time-consuming.
-     * 
+     *
      * @param path The path of the input media file.
      * @throws IllegalArgumentException If the path is invalid.
      */
@@ -113,7 +113,7 @@
      * responsibility to close the file descriptor. It is safe to do so as soon
      * as this call returns. Call this method before the rest of the methods in
      * this class. This method may be time-consuming.
-     * 
+     *
      * @param fd the FileDescriptor for the file you want to play
      * @param offset the offset into the file where the data to be played starts,
      * in bytes. It must be non-negative
@@ -123,13 +123,13 @@
      */
     public native void setDataSource(FileDescriptor fd, long offset, long length)
             throws IllegalArgumentException;
-    
+
     /**
      * Sets the data source (FileDescriptor) to use. It is the caller's
      * responsibility to close the file descriptor. It is safe to do so as soon
      * as this call returns. Call this method before the rest of the methods in
      * this class. This method may be time-consuming.
-     * 
+     *
      * @param fd the FileDescriptor for the file you want to play
      * @throws IllegalArgumentException if the FileDescriptor is invalid
      */
@@ -138,11 +138,11 @@
         // intentionally less than LONG_MAX
         setDataSource(fd, 0, 0x7ffffffffffffffL);
     }
-    
+
     /**
-     * Sets the data source as a content Uri. Call this method before 
+     * Sets the data source as a content Uri. Call this method before
      * the rest of the methods in this class. This method may be time-consuming.
-     * 
+     *
      * @param context the Context to use when resolving the Uri
      * @param uri the Content URI of the data you want to play
      * @throws IllegalArgumentException if the Uri is invalid
@@ -154,7 +154,7 @@
         if (uri == null) {
             throw new IllegalArgumentException();
         }
-        
+
         String scheme = uri.getScheme();
         if(scheme == null || scheme.equals("file")) {
             setDataSource(uri.getPath());
@@ -213,12 +213,12 @@
     /**
      * Call this method after setDataSource(). This method retrieves the
      * meta data value associated with the keyCode.
-     * 
+     *
      * The keyCode currently supported is listed below as METADATA_XXX
      * constants. With any other value, it returns a null pointer.
-     * 
+     *
      * @param keyCode One of the constants listed below at the end of the class.
-     * @return The meta data value associate with the given keyCode on success; 
+     * @return The meta data value associate with the given keyCode on success;
      * null on failure.
      */
     public native String extractMetadata(int keyCode);
@@ -357,6 +357,109 @@
     private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
 
     /**
+     * This method retrieves a video frame by its index. It should only be called
+     * after {@link #setDataSource}.
+     *
+     * @param frameIndex 0-based index of the video frame. The frame index must be that of
+     *        a valid frame. The total number of frames available for retrieval can be queried
+     *        via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+     *
+     * @throws IllegalStateException if the container doesn't contain video or image sequences.
+     * @throws IllegalArgumentException if the requested frame index does not exist.
+     *
+     * @return A Bitmap containing the requested video frame, or null if the retrieval fails.
+     *
+     * @see #getFramesAtIndex(int, int)
+     */
+    public Bitmap getFrameAtIndex(int frameIndex) {
+        Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1);
+        if (bitmaps == null || bitmaps.length < 1) {
+            return null;
+        }
+        return bitmaps[0];
+    }
+
+    /**
+     * This method retrieves a consecutive set of video frames starting at the
+     * specified index. It should only be called after {@link #setDataSource}.
+     *
+     * If the caller intends to retrieve more than one consecutive video frames,
+     * this method is preferred over {@link #getFrameAtIndex(int)} for efficiency.
+     *
+     * @param frameIndex 0-based index of the first video frame to retrieve. The frame index
+     *        must be that of a valid frame. The total number of frames available for retrieval
+     *        can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+     * @param numFrames number of consecutive video frames to retrieve. Must be a positive
+     *        value. The stream must contain at least numFrames frames starting at frameIndex.
+     *
+     * @throws IllegalStateException if the container doesn't contain video or image sequences.
+     * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the
+     *         stream doesn't contain at least numFrames starting at frameIndex.
+
+     * @return An array of Bitmaps containing the requested video frames. The returned
+     *         array could contain less frames than requested if the retrieval fails.
+     *
+     * @see #getFrameAtIndex(int)
+     */
+    public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) {
+        if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) {
+            throw new IllegalStateException("Does not contail video or image sequences");
+        }
+        int frameCount = Integer.parseInt(
+                extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+        if (frameIndex < 0 || numFrames < 1
+                || frameIndex >= frameCount
+                || frameIndex > frameCount - numFrames) {
+            throw new IllegalArgumentException("Invalid frameIndex or numFrames: "
+                + frameIndex + ", " + numFrames);
+        }
+        return _getFrameAtIndex(frameIndex, numFrames);
+    }
+    private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames);
+
+    /**
+     * This method retrieves a still image by its index. It should only be called
+     * after {@link #setDataSource}.
+     *
+     * @param imageIndex 0-based index of the image, with negative value indicating
+     *        the primary image.
+     * @throws IllegalStateException if the container doesn't contain still images.
+     * @throws IllegalArgumentException if the requested image does not exist.
+     *
+     * @return the requested still image, or null if the image cannot be retrieved.
+     *
+     * @see #getPrimaryImage
+     */
+    public Bitmap getImageAtIndex(int imageIndex) {
+        if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) {
+            throw new IllegalStateException("Does not contail still images");
+        }
+
+        String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT);
+        if (imageIndex >= Integer.parseInt(imageCount)) {
+            throw new IllegalArgumentException("Invalid image index: " + imageCount);
+        }
+
+        return _getImageAtIndex(imageIndex);
+    }
+
+    /**
+     * This method retrieves the primary image of the media content. It should only
+     * be called after {@link #setDataSource}.
+     *
+     * @return the primary image, or null if it cannot be retrieved.
+     *
+     * @throws IllegalStateException if the container doesn't contain still images.
+     *
+     * @see #getImageAtIndex(int)
+     */
+    public Bitmap getPrimaryImage() {
+        return getImageAtIndex(-1);
+    }
+
+    private native Bitmap _getImageAtIndex(int imageIndex);
+
+    /**
      * Call this method after setDataSource(). This method finds the optional
      * graphic or album/cover art associated associated with the data source. If
      * there are more than one pictures, (any) one of them is returned.
@@ -572,5 +675,40 @@
      * number.
      */
     public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
+    /**
+     * If this key exists the media contains still image content.
+     */
+    public static final int METADATA_KEY_HAS_IMAGE       = 26;
+    /**
+     * If the media contains still images, this key retrieves the number
+     * of still images.
+     */
+    public static final int METADATA_KEY_IMAGE_COUNT     = 27;
+    /**
+     * If the media contains still images, this key retrieves the image
+     * index of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_PRIMARY   = 28;
+    /**
+     * If the media contains still images, this key retrieves the width
+     * of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_WIDTH     = 29;
+    /**
+     * If the media contains still images, this key retrieves the height
+     * of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_HEIGHT    = 30;
+    /**
+     * If the media contains still images, this key retrieves the rotation
+     * of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_ROTATION  = 31;
+    /**
+     * If the media contains video and this key exists, it retrieves the
+     * total number of frames in the video sequence.
+     */
+    public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32;
+
     // Add more here...
 }
diff --git a/android/media/MediaPlayer.java b/android/media/MediaPlayer.java
index 62757e2..649c091 100644
--- a/android/media/MediaPlayer.java
+++ b/android/media/MediaPlayer.java
@@ -43,6 +43,7 @@
 import android.system.OsConstants;
 import android.util.Log;
 import android.util.Pair;
+import android.util.ArrayMap;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.widget.VideoView;
@@ -58,6 +59,7 @@
 import android.media.SubtitleTrack.RenderingWidget;
 import android.media.SyncParams;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import libcore.io.IoBridge;
@@ -577,6 +579,7 @@
 public class MediaPlayer extends PlayerBase
                          implements SubtitleController.Listener
                                   , VolumeAutomation
+                                  , AudioRouting
 {
     /**
        Constant to retrieve only the new metadata since the last
@@ -1417,6 +1420,155 @@
 
     private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);
 
+    //--------------------------------------------------------------------------
+    // Explicit Routing
+    //--------------------
+    private AudioDeviceInfo mPreferredDevice = null;
+
+    /**
+     * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+     * the output from this MediaPlayer.
+     * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
+     *  If deviceInfo is null, default routing is restored.
+     * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+     * does not correspond to a valid audio device.
+     */
+    @Override
+    public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+        if (deviceInfo != null && !deviceInfo.isSink()) {
+            return false;
+        }
+        int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+        boolean status = native_setOutputDevice(preferredDeviceId);
+        if (status == true) {
+            synchronized (this) {
+                mPreferredDevice = deviceInfo;
+            }
+        }
+        return status;
+    }
+
+    /**
+     * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
+     * is not guaranteed to correspond to the actual device being used for playback.
+     */
+    @Override
+    public AudioDeviceInfo getPreferredDevice() {
+        synchronized (this) {
+            return mPreferredDevice;
+        }
+    }
+
+    /**
+     * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer
+     * Note: The query is only valid if the MediaPlayer is currently playing.
+     * If the player is not playing, the returned device can be null or correspond to previously
+     * selected device when the player was last active.
+     */
+    @Override
+    public AudioDeviceInfo getRoutedDevice() {
+        int deviceId = native_getRoutedDeviceId();
+        if (deviceId == 0) {
+            return null;
+        }
+        AudioDeviceInfo[] devices =
+                AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
+        for (int i = 0; i < devices.length; i++) {
+            if (devices[i].getId() == deviceId) {
+                return devices[i];
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+     */
+    private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+        if (mRoutingChangeListeners.size() == 0) {
+            native_enableDeviceCallback(enabled);
+        }
+    }
+
+    /**
+     * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+     * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+     * by an app to receive (re)routing notifications.
+     */
+    @GuardedBy("mRoutingChangeListeners")
+    private ArrayMap<AudioRouting.OnRoutingChangedListener,
+            NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+    /**
+     * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+     * changes on this MediaPlayer.
+     * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+     * notifications of rerouting events.
+     * @param handler  Specifies the {@link Handler} object for the thread on which to execute
+     * the callback. If <code>null</code>, the handler on the main looper will be used.
+     */
+    @Override
+    public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+            Handler handler) {
+        synchronized (mRoutingChangeListeners) {
+            if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+                enableNativeRoutingCallbacksLocked(true);
+                mRoutingChangeListeners.put(
+                        listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
+            }
+        }
+    }
+
+    /**
+     * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+     * to receive rerouting notifications.
+     * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+     * to remove.
+     */
+    @Override
+    public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+        synchronized (mRoutingChangeListeners) {
+            if (mRoutingChangeListeners.containsKey(listener)) {
+                mRoutingChangeListeners.remove(listener);
+                enableNativeRoutingCallbacksLocked(false);
+            }
+        }
+    }
+
+    /**
+     * Helper class to handle the forwarding of native events to the appropriate listener
+     * (potentially) handled in a different thread
+     */
+    private class NativeRoutingEventHandlerDelegate {
+        private MediaPlayer mMediaPlayer;
+        private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
+        private Handler mHandler;
+
+        NativeRoutingEventHandlerDelegate(final MediaPlayer mediaPlayer,
+                final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
+            mMediaPlayer = mediaPlayer;
+            mOnRoutingChangedListener = listener;
+            mHandler = handler != null ? handler : mEventHandler;
+        }
+
+        void notifyClient() {
+            if (mHandler != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mOnRoutingChangedListener != null) {
+                            mOnRoutingChangedListener.onRoutingChanged(mMediaPlayer);
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    private native final boolean native_setOutputDevice(int deviceId);
+    private native final int native_getRoutedDeviceId();
+    private native final void native_enableDeviceCallback(boolean enabled);
+
     /**
      * Set the low-level power management behavior for this MediaPlayer.  This
      * can be used when the MediaPlayer is not playing through a SurfaceHolder
@@ -1546,21 +1698,9 @@
     public native boolean isPlaying();
 
     /**
-     * Gets the default buffering management params.
-     * Calling it only after {@code setDataSource} has been called.
-     * Each type of data source might have different set of default params.
-     *
-     * @return the default buffering management params supported by the source component.
-     * @throws IllegalStateException if the internal player engine has not been
-     * initialized, or {@code setDataSource} has not been called.
-     * @hide
-     */
-    @NonNull
-    public native BufferingParams getDefaultBufferingParams();
-
-    /**
      * Gets the current buffering management params used by the source component.
      * Calling it only after {@code setDataSource} has been called.
+     * Each type of data source might have different set of default params.
      *
      * @return the current buffering management params used by the source component.
      * @throws IllegalStateException if the internal player engine has not been
@@ -1575,8 +1715,7 @@
      * The object sets its internal BufferingParams to the input, except that the input is
      * invalid or not supported.
      * Call it only after {@code setDataSource} has been called.
-     * Users should only use supported mode returned by {@link #getDefaultBufferingParams()}
-     * or its downsized version as described in {@link BufferingParams}.
+     * The input is a hint to MediaPlayer.
      *
      * @param params the buffering management params.
      *
@@ -3176,6 +3315,7 @@
     private static final int MEDIA_SUBTITLE_DATA = 201;
     private static final int MEDIA_META_DATA = 202;
     private static final int MEDIA_DRM_INFO = 210;
+    private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000;
 
     private TimeProvider mTimeProvider;
 
@@ -3414,6 +3554,16 @@
             case MEDIA_NOP: // interface test message - ignore
                 break;
 
+            case MEDIA_AUDIO_ROUTING_CHANGED:
+                AudioManager.resetAudioPortGeneration();
+                synchronized (mRoutingChangeListeners) {
+                    for (NativeRoutingEventHandlerDelegate delegate
+                            : mRoutingChangeListeners.values()) {
+                        delegate.notifyClient();
+                    }
+                }
+                return;
+
             default:
                 Log.e(TAG, "Unknown message type " + msg.what);
                 return;
diff --git a/android/net/ConnectivityManager.java b/android/net/ConnectivityManager.java
index d7ecc81..8071e8b 100644
--- a/android/net/ConnectivityManager.java
+++ b/android/net/ConnectivityManager.java
@@ -619,6 +619,35 @@
      */
     public static final int NETID_UNSET = 0;
 
+    /**
+     * Private DNS Mode values.
+     *
+     * The "private_dns_mode" global setting stores a String value which is
+     * expected to be one of the following.
+     */
+
+    /**
+     * @hide
+     */
+    public static final String PRIVATE_DNS_MODE_OFF = "off";
+    /**
+     * @hide
+     */
+    public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+    /**
+     * @hide
+     */
+    public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+    /**
+     * The default Private DNS mode.
+     *
+     * This may change from release to release or may become dependent upon
+     * the capabilities of the underlying platform.
+     *
+     * @hide
+     */
+    public static final String PRIVATE_DNS_DEFAULT_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
     private final IConnectivityManager mService;
     /**
      * A kludge to facilitate static access where a Context pointer isn't available, like in the
diff --git a/android/net/ConnectivityMetricsEvent.java b/android/net/ConnectivityMetricsEvent.java
index 46bb346..394ac42 100644
--- a/android/net/ConnectivityMetricsEvent.java
+++ b/android/net/ConnectivityMetricsEvent.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+
 import com.android.internal.util.BitUtils;
 
 /**
@@ -80,7 +81,7 @@
         StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
         buffer.append(String.format("%tT.%tL", timestamp, timestamp));
         if (netId != 0) {
-            buffer.append(", ").append(netId);
+            buffer.append(", ").append("netId=").append(netId);
         }
         if (ifname != null) {
             buffer.append(", ").append(ifname);
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 16b1452..64f8f39 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -78,7 +78,11 @@
     /**
      * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
      *
-     * <p>Valid lengths for this key are {128, 192, 256}.
+     * <p>Valid lengths for keying material are {160, 224, 288}.
+     *
+     * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key
+     * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per
+     * invocation with the same key.
      *
      * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
      */
diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java
index d7b3256..eccd5f4 100644
--- a/android/net/IpSecManager.java
+++ b/android/net/IpSecManager.java
@@ -136,7 +136,7 @@
         }
 
         @Override
-        protected void finalize() {
+        protected void finalize() throws Throwable {
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
diff --git a/android/net/LocalSocketImpl.java b/android/net/LocalSocketImpl.java
index 05c8afb..6e4a231 100644
--- a/android/net/LocalSocketImpl.java
+++ b/android/net/LocalSocketImpl.java
@@ -16,18 +16,18 @@
 
 package android.net;
 
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.InputStream;
-import java.io.FileDescriptor;
-import java.net.SocketOptions;
-
 import android.system.ErrnoException;
+import android.system.Int32Ref;
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructLinger;
 import android.system.StructTimeval;
-import android.util.MutableInt;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketOptions;
 
 /**
  * Socket implementation used for android.net.LocalSocket and
@@ -62,7 +62,7 @@
             FileDescriptor myFd = fd;
             if (myFd == null) throw new IOException("socket closed");
 
-            MutableInt avail = new MutableInt(0);
+            Int32Ref avail = new Int32Ref(0);
             try {
                 Os.ioctlInt(myFd, OsConstants.FIONREAD, avail);
             } catch (ErrnoException e) {
@@ -167,7 +167,7 @@
             if (myFd == null) throw new IOException("socket closed");
 
             // Loop until the output buffer is empty.
-            MutableInt pending = new MutableInt(0);
+            Int32Ref pending = new Int32Ref(0);
             while (true) {
                 try {
                     // See linux/net/unix/af_unix.c
diff --git a/android/net/MacAddress.java b/android/net/MacAddress.java
new file mode 100644
index 0000000..f6a69ba
--- /dev/null
+++ b/android/net/MacAddress.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.StringJoiner;
+
+/**
+ * Represents a mac address.
+ *
+ * @hide
+ */
+public final class MacAddress implements Parcelable {
+
+    private static final int ETHER_ADDR_LEN = 6;
+    private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
+
+    /** The broadcast mac address.  */
+    public static final MacAddress BROADCAST_ADDRESS = new MacAddress(ETHER_ADDR_BROADCAST);
+
+    /** The zero mac address. */
+    public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
+
+    /** Represents categories of mac addresses. */
+    public enum MacAddressType {
+        UNICAST,
+        MULTICAST,
+        BROADCAST;
+    }
+
+    private static final long VALID_LONG_MASK = BROADCAST_ADDRESS.mAddr;
+    private static final long LOCALLY_ASSIGNED_MASK = new MacAddress("2:0:0:0:0:0").mAddr;
+    private static final long MULTICAST_MASK = new MacAddress("1:0:0:0:0:0").mAddr;
+    private static final long OUI_MASK = new MacAddress("ff:ff:ff:0:0:0").mAddr;
+    private static final long NIC_MASK = new MacAddress("0:0:0:ff:ff:ff").mAddr;
+    private static final MacAddress BASE_ANDROID_MAC = new MacAddress("da:a1:19:0:0:0");
+
+    // Internal representation of the mac address as a single 8 byte long.
+    // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
+    // mac address are encoded in the 6 least significant bytes of the long, where the first
+    // byte of the array is mapped to the 3rd highest logical byte of the long, the second
+    // byte of the array is mapped to the 4th highest logical byte of the long, and so on.
+    private final long mAddr;
+
+    private MacAddress(long addr) {
+        mAddr = addr;
+    }
+
+    /** Creates a MacAddress for the given byte representation. */
+    public MacAddress(byte[] addr) {
+        this(longAddrFromByteAddr(addr));
+    }
+
+    /** Creates a MacAddress for the given string representation. */
+    public MacAddress(String addr) {
+        this(longAddrFromByteAddr(byteAddrFromStringAddr(addr)));
+    }
+
+    /** Returns the MacAddressType of this MacAddress. */
+    public MacAddressType addressType() {
+        if (equals(BROADCAST_ADDRESS)) {
+            return MacAddressType.BROADCAST;
+        }
+        if (isMulticastAddress()) {
+            return MacAddressType.MULTICAST;
+        }
+        return MacAddressType.UNICAST;
+    }
+
+    /** Returns true if this MacAddress corresponds to a multicast address. */
+    public boolean isMulticastAddress() {
+        return (mAddr & MULTICAST_MASK) != 0;
+    }
+
+    /** Returns true if this MacAddress corresponds to a locally assigned address. */
+    public boolean isLocallyAssigned() {
+        return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
+    }
+
+    /** Returns a byte array representation of this MacAddress. */
+    public byte[] toByteArray() {
+        return byteAddrFromLongAddr(mAddr);
+    }
+
+    @Override
+    public String toString() {
+        return stringAddrFromByteAddr(byteAddrFromLongAddr(mAddr));
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) ((mAddr >> 32) ^ mAddr);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mAddr);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<MacAddress> CREATOR =
+            new Parcelable.Creator<MacAddress>() {
+                public MacAddress createFromParcel(Parcel in) {
+                    return new MacAddress(in.readLong());
+                }
+
+                public MacAddress[] newArray(int size) {
+                    return new MacAddress[size];
+                }
+            };
+
+    /** Return true if the given byte array is not null and has the length of a mac address. */
+    public static boolean isMacAddress(byte[] addr) {
+        return addr != null && addr.length == ETHER_ADDR_LEN;
+    }
+
+    /**
+     * Return the MacAddressType of the mac address represented by the given byte array,
+     * or null if the given byte array does not represent an mac address.
+     */
+    public static MacAddressType macAddressType(byte[] addr) {
+        if (!isMacAddress(addr)) {
+            return null;
+        }
+        return new MacAddress(addr).addressType();
+    }
+
+    /** DOCME */
+    public static byte[] byteAddrFromStringAddr(String addr) {
+        if (addr == null) {
+            throw new IllegalArgumentException("cannot convert the null String");
+        }
+        String[] parts = addr.split(":");
+        if (parts.length != ETHER_ADDR_LEN) {
+            throw new IllegalArgumentException(addr + " was not a valid MAC address");
+        }
+        byte[] bytes = new byte[ETHER_ADDR_LEN];
+        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+            int x = Integer.valueOf(parts[i], 16);
+            if (x < 0 || 0xff < x) {
+                throw new IllegalArgumentException(addr + "was not a valid MAC address");
+            }
+            bytes[i] = (byte) x;
+        }
+        return bytes;
+    }
+
+    /** DOCME */
+    public static String stringAddrFromByteAddr(byte[] addr) {
+        if (!isMacAddress(addr)) {
+            return null;
+        }
+        StringJoiner j = new StringJoiner(":");
+        for (byte b : addr) {
+            j.add(Integer.toHexString(BitUtils.uint8(b)));
+        }
+        return j.toString();
+    }
+
+    /** @hide */
+    public static byte[] byteAddrFromLongAddr(long addr) {
+        byte[] bytes = new byte[ETHER_ADDR_LEN];
+        int index = ETHER_ADDR_LEN;
+        while (index-- > 0) {
+            bytes[index] = (byte) addr;
+            addr = addr >> 8;
+        }
+        return bytes;
+    }
+
+    /** @hide */
+    public static long longAddrFromByteAddr(byte[] addr) {
+        if (!isMacAddress(addr)) {
+            throw new IllegalArgumentException(
+                    Arrays.toString(addr) + " was not a valid MAC address");
+        }
+        long longAddr = 0;
+        for (byte b : addr) {
+            longAddr = (longAddr << 8) + BitUtils.uint8(b);
+        }
+        return longAddr;
+    }
+
+    /** @hide */
+    public static long longAddrFromStringAddr(String addr) {
+        if (addr == null) {
+            throw new IllegalArgumentException("cannot convert the null String");
+        }
+        String[] parts = addr.split(":");
+        if (parts.length != ETHER_ADDR_LEN) {
+            throw new IllegalArgumentException(addr + " was not a valid MAC address");
+        }
+        long longAddr = 0;
+        int index = ETHER_ADDR_LEN;
+        while (index-- > 0) {
+            int x = Integer.valueOf(parts[index], 16);
+            if (x < 0 || 0xff < x) {
+                throw new IllegalArgumentException(addr + "was not a valid MAC address");
+            }
+            longAddr = x + (longAddr << 8);
+        }
+        return longAddr;
+    }
+
+    /** @hide */
+    public static String stringAddrFromLongAddr(long addr) {
+        addr = Long.reverseBytes(addr) >> 16;
+        StringJoiner j = new StringJoiner(":");
+        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+            j.add(Integer.toHexString((byte) addr));
+            addr = addr >> 8;
+        }
+        return j.toString();
+    }
+
+    /**
+     * Returns a randomely generated mac address with the Android OUI value "DA-A1-19".
+     * The locally assigned bit is always set to 1.
+     */
+    public static MacAddress getRandomAddress() {
+        return getRandomAddress(BASE_ANDROID_MAC, new Random());
+    }
+
+    /**
+     * Returns a randomely generated mac address using the given Random object and the same
+     * OUI values as the given MacAddress. The locally assigned bit is always set to 1.
+     */
+    public static MacAddress getRandomAddress(MacAddress base, Random r) {
+        long longAddr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()) | LOCALLY_ASSIGNED_MASK;
+        return new MacAddress(longAddr);
+    }
+
+    // Convenience function for working around the lack of byte literals.
+    private static byte[] addr(int... in) {
+        if (in.length != ETHER_ADDR_LEN) {
+            throw new IllegalArgumentException(Arrays.toString(in)
+                    + " was not an array with length equal to " + ETHER_ADDR_LEN);
+        }
+        byte[] out = new byte[ETHER_ADDR_LEN];
+        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+            out[i] = (byte) in[i];
+        }
+        return out;
+    }
+}
diff --git a/android/net/Network.java b/android/net/Network.java
index 3c868c3..903b602 100644
--- a/android/net/Network.java
+++ b/android/net/Network.java
@@ -16,14 +16,14 @@
 
 package android.net;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 
-import libcore.net.http.Dns;
-import libcore.net.http.HttpURLConnectionFactory;
+import com.android.okhttp.internalandroidapi.Dns;
+import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -34,11 +34,12 @@
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
-import java.net.UnknownHostException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.net.UnknownHostException;
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
+
 import javax.net.SocketFactory;
 
 /**
diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java
index db12dd9..ee75fd4 100644
--- a/android/net/NetworkCapabilities.java
+++ b/android/net/NetworkCapabilities.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.annotation.IntDef;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -30,15 +31,24 @@
 import java.util.StringJoiner;
 
 /**
- * This class represents the capabilities of a network.  This is used both to specify
- * needs to {@link ConnectivityManager} and when inspecting a network.
- *
- * Note that this replaces the old {@link ConnectivityManager#TYPE_MOBILE} method
- * of network selection.  Rather than indicate a need for Wi-Fi because an application
- * needs high bandwidth and risk obsolescence when a new, fast network appears (like LTE),
- * the application should specify it needs high bandwidth.  Similarly if an application
- * needs an unmetered network for a bulk transfer it can specify that rather than assuming
- * all cellular based connections are metered and all Wi-Fi based connections are not.
+ * Representation of the capabilities of a network. This object serves two
+ * purposes:
+ * <ul>
+ * <li>An expression of the current capabilities of an active network, typically
+ * expressed through
+ * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
+ * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
+ * <li>An expression of the future capabilities of a desired network, typically
+ * expressed through {@link NetworkRequest}.
+ * </ul>
+ * <p>
+ * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
+ * network selection. Rather than indicate a need for Wi-Fi because an
+ * application needs high bandwidth and risk obsolescence when a new, fast
+ * network appears (like LTE), the application should specify it needs high
+ * bandwidth. Similarly if an application needs an unmetered network for a bulk
+ * transfer it can specify that rather than assuming all cellular based
+ * connections are metered and all Wi-Fi based connections are not.
  */
 public final class NetworkCapabilities implements Parcelable {
     private static final String TAG = "NetworkCapabilities";
@@ -101,6 +111,7 @@
             NET_CAPABILITY_NOT_VPN,
             NET_CAPABILITY_VALIDATED,
             NET_CAPABILITY_CAPTIVE_PORTAL,
+            NET_CAPABILITY_NOT_ROAMING,
             NET_CAPABILITY_FOREGROUND,
     })
     public @interface NetCapability { }
@@ -218,11 +229,16 @@
     public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
 
     /**
+     * Indicates that this network is not roaming.
+     */
+    public static final int NET_CAPABILITY_NOT_ROAMING = 18;
+
+    /**
      * Indicates that this network is available for use by apps, and not a network that is being
      * kept up in the background to facilitate fast network switching.
      * @hide
      */
-    public static final int NET_CAPABILITY_FOREGROUND = 18;
+    public static final int NET_CAPABILITY_FOREGROUND = 19;
 
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
     private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_FOREGROUND;
@@ -237,6 +253,7 @@
             (1 << NET_CAPABILITY_TRUSTED) |
             (1 << NET_CAPABILITY_VALIDATED) |
             (1 << NET_CAPABILITY_CAPTIVE_PORTAL) |
+            (1 << NET_CAPABILITY_NOT_ROAMING) |
             (1 << NET_CAPABILITY_FOREGROUND);
 
     /**
@@ -316,6 +333,21 @@
     }
 
     /**
+     * Sets (or clears) the given capability on this {@link NetworkCapabilities}
+     * instance.
+     *
+     * @hide
+     */
+    public NetworkCapabilities setCapability(@NetCapability int capability, boolean value) {
+        if (value) {
+            addCapability(capability);
+        } else {
+            removeCapability(capability);
+        }
+        return this;
+    }
+
+    /**
      * Gets all the capabilities set on this {@code NetworkCapability} instance.
      *
      * @return an array of capability values for this instance.
@@ -326,6 +358,15 @@
     }
 
     /**
+     * Sets all the capabilities set on this {@code NetworkCapability} instance.
+     *
+     * @hide
+     */
+    public void setCapabilities(@NetCapability int[] capabilities) {
+        mNetworkCapabilities = BitUtils.packBits(capabilities);
+    }
+
+    /**
      * Tests for the presence of a capabilitity on this instance.
      *
      * @param capability the capabilities to be tested for.
@@ -515,6 +556,21 @@
     }
 
     /**
+     * Sets (or clears) the given transport on this {@link NetworkCapabilities}
+     * instance.
+     *
+     * @hide
+     */
+    public NetworkCapabilities setTransportType(@Transport int transportType, boolean value) {
+        if (value) {
+            addTransportType(transportType);
+        } else {
+            removeTransportType(transportType);
+        }
+        return this;
+    }
+
+    /**
      * Gets all the transports set on this {@code NetworkCapability} instance.
      *
      * @return an array of transport type values for this instance.
@@ -525,6 +581,15 @@
     }
 
     /**
+     * Sets all the transports set on this {@code NetworkCapability} instance.
+     *
+     * @hide
+     */
+    public void setTransportTypes(@Transport int[] transportTypes) {
+        mTransportTypes = BitUtils.packBits(transportTypes);
+    }
+
+    /**
      * Tests for the presence of a transport on this instance.
      *
      * @param transportType the transport type to be tested for.
@@ -549,12 +614,18 @@
     }
 
     /**
+     * Value indicating that link bandwidth is unspecified.
+     * @hide
+     */
+    public static final int LINK_BANDWIDTH_UNSPECIFIED = 0;
+
+    /**
      * Passive link bandwidth.  This is a rough guide of the expected peak bandwidth
      * for the first hop on the given transport.  It is not measured, but may take into account
      * link parameters (Radio technology, allocated channels, etc).
      */
-    private int mLinkUpBandwidthKbps;
-    private int mLinkDownBandwidthKbps;
+    private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+    private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
 
     /**
      * Sets the upstream bandwidth for this network in Kbps.  This always only refers to
@@ -571,8 +642,9 @@
      * @param upKbps the estimated first hop upstream (device to network) bandwidth.
      * @hide
      */
-    public void setLinkUpstreamBandwidthKbps(int upKbps) {
+    public NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) {
         mLinkUpBandwidthKbps = upKbps;
+        return this;
     }
 
     /**
@@ -600,8 +672,9 @@
      * @param downKbps the estimated first hop downstream (network to device) bandwidth.
      * @hide
      */
-    public void setLinkDownstreamBandwidthKbps(int downKbps) {
+    public NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) {
         mLinkDownBandwidthKbps = downKbps;
+        return this;
     }
 
     /**
@@ -628,6 +701,20 @@
         return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps &&
                 this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
     }
+    /** @hide */
+    public static int minBandwidth(int a, int b) {
+        if (a == LINK_BANDWIDTH_UNSPECIFIED)  {
+            return b;
+        } else if (b == LINK_BANDWIDTH_UNSPECIFIED) {
+            return a;
+        } else {
+            return Math.min(a, b);
+        }
+    }
+    /** @hide */
+    public static int maxBandwidth(int a, int b) {
+        return Math.max(a, b);
+    }
 
     private NetworkSpecifier mNetworkSpecifier = null;
 
@@ -708,8 +795,9 @@
      * @param signalStrength the bearer-specific signal strength.
      * @hide
      */
-    public void setSignalStrength(int signalStrength) {
+    public NetworkCapabilities setSignalStrength(int signalStrength) {
         mSignalStrength = signalStrength;
+        return this;
     }
 
     /**
@@ -968,6 +1056,7 @@
             case NET_CAPABILITY_NOT_VPN:        return "NOT_VPN";
             case NET_CAPABILITY_VALIDATED:      return "VALIDATED";
             case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+            case NET_CAPABILITY_NOT_ROAMING:    return "NOT_ROAMING";
             case NET_CAPABILITY_FOREGROUND:     return "FOREGROUND";
             default:                            return Integer.toString(capability);
         }
diff --git a/android/net/NetworkIdentity.java b/android/net/NetworkIdentity.java
index acd7b56..d3b3599 100644
--- a/android/net/NetworkIdentity.java
+++ b/android/net/NetworkIdentity.java
@@ -189,7 +189,8 @@
 
         String subscriberId = null;
         String networkId = null;
-        boolean roaming = false;
+        boolean roaming = !state.networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         boolean metered = !state.networkCapabilities.hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
 
@@ -203,7 +204,6 @@
             }
 
             subscriberId = state.subscriberId;
-            roaming = state.networkInfo.isRoaming();
 
         } else if (type == TYPE_WIFI) {
             if (state.networkId != null) {
diff --git a/android/net/NetworkInfo.java b/android/net/NetworkInfo.java
index 818aa21..e6ad89a 100644
--- a/android/net/NetworkInfo.java
+++ b/android/net/NetworkInfo.java
@@ -305,11 +305,17 @@
     }
 
     /**
-     * Indicates whether the device is currently roaming on this network.
-     * When {@code true}, it suggests that use of data on this network
-     * may incur extra costs.
+     * Indicates whether the device is currently roaming on this network. When
+     * {@code true}, it suggests that use of data on this network may incur
+     * extra costs.
+     *
      * @return {@code true} if roaming is in effect, {@code false} otherwise.
+     * @deprecated Callers should switch to checking
+     *             {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING}
+     *             instead, since that handles more complex situations, such as
+     *             VPNs.
      */
+    @Deprecated
     public boolean isRoaming() {
         synchronized (this) {
             return mIsRoaming;
@@ -318,6 +324,7 @@
 
     /** {@hide} */
     @VisibleForTesting
+    @Deprecated
     public void setRoaming(boolean isRoaming) {
         synchronized (this) {
             mIsRoaming = isRoaming;
diff --git a/android/net/NetworkWatchlistManager.java b/android/net/NetworkWatchlistManager.java
new file mode 100644
index 0000000..42e43c8
--- /dev/null
+++ b/android/net/NetworkWatchlistManager.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.net;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that manage network watchlist in system.
+ * @hide
+ */
+@SystemService(Context.NETWORK_WATCHLIST_SERVICE)
+public class NetworkWatchlistManager {
+
+    private static final String TAG = "NetworkWatchlistManager";
+    private static final String SHARED_MEMORY_TAG = "NETWORK_WATCHLIST_SHARED_MEMORY";
+
+    private final Context mContext;
+    private final INetworkWatchlistManager mNetworkWatchlistManager;
+
+    /**
+     * @hide
+     */
+    public NetworkWatchlistManager(Context context, INetworkWatchlistManager manager) {
+        mContext = context;
+        mNetworkWatchlistManager = manager;
+    }
+
+    /**
+     * @hide
+     */
+    public NetworkWatchlistManager(Context context) {
+        mContext = Preconditions.checkNotNull(context, "missing context");
+        mNetworkWatchlistManager = (INetworkWatchlistManager)
+                INetworkWatchlistManager.Stub.asInterface(
+                        ServiceManager.getService(Context.NETWORK_WATCHLIST_SERVICE));
+    }
+
+    /**
+     * Report network watchlist records if necessary.
+     *
+     * Watchlist report process will run summarize records into a single report, then the
+     * report will be processed by differential privacy framework and store it on disk.
+     *
+     * @hide
+     */
+    public void reportWatchlistIfNecessary() {
+        try {
+            mNetworkWatchlistManager.reportWatchlistIfNecessary();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Cannot report records", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/android/net/SSLCertificateSocketFactory.java b/android/net/SSLCertificateSocketFactory.java
index 0b1569c..4817813 100644
--- a/android/net/SSLCertificateSocketFactory.java
+++ b/android/net/SSLCertificateSocketFactory.java
@@ -63,7 +63,12 @@
  * This implementation does check the server's certificate hostname, but only
  * for createSocket variants that specify a hostname.  When using methods that
  * use {@link InetAddress} or which return an unconnected socket, you MUST
- * verify the server's identity yourself to ensure a secure connection.</p>
+ * verify the server's identity yourself to ensure a secure connection.
+ *
+ * Refer to
+ * <a href="https://developer.android.com/training/articles/security-gms-provider.html">
+ * Updating Your Security Provider to Protect Against SSL Exploits</a>
+ * for further information.</p>
  *
  * <p>One way to verify the server's identity is to use
  * {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
diff --git a/android/net/Uri.java b/android/net/Uri.java
index d5377c7..9edcc0e 100644
--- a/android/net/Uri.java
+++ b/android/net/Uri.java
@@ -1066,7 +1066,7 @@
                 return null;
             }
 
-            int end = authority.indexOf('@');
+            int end = authority.lastIndexOf('@');
             return end == NOT_FOUND ? null : authority.substring(0, end);
         }
 
@@ -1090,7 +1090,7 @@
             }
 
             // Parse out user info and then port.
-            int userInfoSeparator = authority.indexOf('@');
+            int userInfoSeparator = authority.lastIndexOf('@');
             int portSeparator = authority.indexOf(':', userInfoSeparator);
 
             String encodedHost = portSeparator == NOT_FOUND
@@ -1116,7 +1116,7 @@
 
             // Make sure we look for the port separtor *after* the user info
             // separator. We have URLs with a ':' in the user info.
-            int userInfoSeparator = authority.indexOf('@');
+            int userInfoSeparator = authority.lastIndexOf('@');
             int portSeparator = authority.indexOf(':', userInfoSeparator);
 
             if (portSeparator == NOT_FOUND) {
diff --git a/android/net/apf/ApfFilter.java b/android/net/apf/ApfFilter.java
index 5c2b66f..31a1abb 100644
--- a/android/net/apf/ApfFilter.java
+++ b/android/net/apf/ApfFilter.java
@@ -86,6 +86,14 @@
  */
 public class ApfFilter {
 
+    // Helper class for specifying functional filter parameters.
+    public static class ApfConfiguration {
+        public ApfCapabilities apfCapabilities;
+        public boolean multicastFilter;
+        public boolean ieee802_3Filter;
+        public int[] ethTypeBlackList;
+    }
+
     // Enums describing the outcome of receiving an RA packet.
     private static enum ProcessRaResult {
         MATCH,          // Received RA matched a known RA
@@ -261,17 +269,16 @@
     private int mIPv4PrefixLength;
 
     @VisibleForTesting
-    ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
-            IpClient.Callback ipClientCallback, boolean multicastFilter,
-            boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
-        mApfCapabilities = apfCapabilities;
+    ApfFilter(ApfConfiguration config, NetworkInterface networkInterface,
+            IpClient.Callback ipClientCallback, IpConnectivityLog log) {
+        mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
         mNetworkInterface = networkInterface;
-        mMulticastFilter = multicastFilter;
-        mDrop802_3Frames = ieee802_3Filter;
+        mMulticastFilter = config.multicastFilter;
+        mDrop802_3Frames = config.ieee802_3Filter;
 
         // Now fill the black list from the passed array
-        mEthTypeBlackList = filterEthTypeBlackList(ethTypeBlackList);
+        mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
 
         mMetricsLog = log;
 
@@ -1160,9 +1167,10 @@
      * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
      * filtering using APF programs.
      */
-    public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
-            NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
-            boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
+    public static ApfFilter maybeCreate(ApfConfiguration config,
+            NetworkInterface networkInterface, IpClient.Callback ipClientCallback) {
+        if (config == null) return null;
+        ApfCapabilities apfCapabilities =  config.apfCapabilities;
         if (apfCapabilities == null || networkInterface == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
         if (apfCapabilities.maximumApfProgramSize < 512) {
@@ -1178,8 +1186,7 @@
             Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
             return null;
         }
-        return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
-                multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
+        return new ApfFilter(config, networkInterface, ipClientCallback, new IpConnectivityLog());
     }
 
     public synchronized void shutdown() {
diff --git a/android/net/ip/IpClient.java b/android/net/ip/IpClient.java
index 2359fab..70983c8 100644
--- a/android/net/ip/IpClient.java
+++ b/android/net/ip/IpClient.java
@@ -310,12 +310,12 @@
                 return this;
             }
 
-            public Builder withIPv6AddrGenModeEUI64() {
+            public Builder withRandomMacAddress() {
                 mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
                 return this;
             }
 
-            public Builder withIPv6AddrGenModeStablePrivacy() {
+            public Builder withStableMacAddress() {
                 mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
                 return this;
             }
@@ -1429,15 +1429,15 @@
 
         @Override
         public void enter() {
+            ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+            apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+            apfConfig.multicastFilter = mMulticastFiltering;
             // Get the Configuration for ApfFilter from Context
-            final boolean filter802_3Frames =
+            apfConfig.ieee802_3Filter =
                     mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
-            final int[] ethTypeBlackList = mContext.getResources().getIntArray(
-                    R.array.config_apfEthTypeBlackList);
-
-            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
-                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+            apfConfig.ethTypeBlackList =
+                    mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+            mApfFilter = ApfFilter.maybeCreate(apfConfig, mNetworkInterface, mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index b12cb32..3898145 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -88,16 +88,6 @@
                 return this;
             }
             @Override
-            public Builder withIPv6AddrGenModeEUI64() {
-                super.withIPv6AddrGenModeEUI64();
-                return this;
-            }
-            @Override
-            public Builder withIPv6AddrGenModeStablePrivacy() {
-                super.withIPv6AddrGenModeStablePrivacy();
-                return this;
-            }
-            @Override
             public Builder withNetwork(Network network) {
                 super.withNetwork(network);
                 return this;
diff --git a/android/net/metrics/ConnectStats.java b/android/net/metrics/ConnectStats.java
index 2495cab..b320b75 100644
--- a/android/net/metrics/ConnectStats.java
+++ b/android/net/metrics/ConnectStats.java
@@ -119,7 +119,8 @@
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder("ConnectStats(").append(netId).append(", ");
+        StringBuilder builder =
+                new StringBuilder("ConnectStats(").append("netId=").append(netId).append(", ");
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
diff --git a/android/net/metrics/DefaultNetworkEvent.java b/android/net/metrics/DefaultNetworkEvent.java
index eb61c15..8ff8e4f 100644
--- a/android/net/metrics/DefaultNetworkEvent.java
+++ b/android/net/metrics/DefaultNetworkEvent.java
@@ -20,44 +20,72 @@
 
 import android.net.NetworkCapabilities;
 
+import com.android.internal.util.BitUtils;
+
+import java.util.StringJoiner;
+
 /**
  * An event recorded by ConnectivityService when there is a change in the default network.
  * {@hide}
  */
 public class DefaultNetworkEvent {
 
-    // The ID of the network that has become the new default or NETID_UNSET if none.
+    // The creation time in milliseconds of this DefaultNetworkEvent.
+    public final long creationTimeMs;
+    // The network ID of the network or NETID_UNSET if none.
     public int netId = NETID_UNSET;
-    // The list of transport types of the new default network, for example TRANSPORT_WIFI, as
-    // defined in NetworkCapabilities.java.
-    public int[] transportTypes = new int[0];
-    // The ID of the network that was the default before or NETID_UNSET if none.
-    public int prevNetId = NETID_UNSET;
-    // Whether the previous network had IPv4/IPv6 connectivity.
-    public boolean prevIPv4;
-    public boolean prevIPv6;
+    // The list of transport types, as defined in NetworkCapabilities.java.
+    public int transports;
+    // The list of transport types of the last previous default network.
+    public int previousTransports;
+    // Whether the network has IPv4/IPv6 connectivity.
+    public boolean ipv4;
+    public boolean ipv6;
+    // The initial network score when this network became the default network.
+    public int initialScore;
+    // The initial network score when this network stopped being the default network.
+    public int finalScore;
+    // The total duration in milliseconds this network was the default network.
+    public long durationMs;
+    // The total duration in milliseconds this network was the default network and was validated.
+    public long validatedMs;
+
+    public DefaultNetworkEvent(long timeMs) {
+        creationTimeMs = timeMs;
+    }
+
+    /** Update the durationMs of this DefaultNetworkEvent for the given current time. */
+    public void updateDuration(long timeMs) {
+        durationMs = timeMs - creationTimeMs;
+    }
 
     @Override
     public String toString() {
-        String prevNetwork = String.valueOf(prevNetId);
-        String newNetwork = String.valueOf(netId);
-        if (prevNetId != 0) {
-            prevNetwork += ":" + ipSupport();
+        StringJoiner j = new StringJoiner(", ", "DefaultNetworkEvent(", ")");
+        j.add("netId=" + netId);
+        for (int t : BitUtils.unpackBits(transports)) {
+            j.add(NetworkCapabilities.transportNameOf(t));
         }
-        if (netId != 0) {
-            newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+        j.add("ip=" + ipSupport());
+        if (initialScore > 0) {
+            j.add("initial_score=" + initialScore);
         }
-        return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+        if (finalScore > 0) {
+            j.add("final_score=" + finalScore);
+        }
+        j.add(String.format("duration=%.0fs", durationMs / 1000.0));
+        j.add(String.format("validation=%4.1f%%", (validatedMs * 100.0) / durationMs));
+        return j.toString();
     }
 
     private String ipSupport() {
-        if (prevIPv4 && prevIPv6) {
+        if (ipv4 && ipv6) {
             return "IPv4v6";
         }
-        if (prevIPv6) {
+        if (ipv6) {
             return "IPv6";
         }
-        if (prevIPv4) {
+        if (ipv4) {
             return "IPv4";
         }
         return "NONE";
diff --git a/android/net/metrics/DnsEvent.java b/android/net/metrics/DnsEvent.java
index 81b098b..5aa705b 100644
--- a/android/net/metrics/DnsEvent.java
+++ b/android/net/metrics/DnsEvent.java
@@ -85,7 +85,8 @@
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder("DnsEvent(").append(netId).append(", ");
+        StringBuilder builder =
+                new StringBuilder("DnsEvent(").append("netId=").append(netId).append(", ");
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
diff --git a/android/net/metrics/NetworkEvent.java b/android/net/metrics/NetworkEvent.java
index 4df3bf0..1999e78 100644
--- a/android/net/metrics/NetworkEvent.java
+++ b/android/net/metrics/NetworkEvent.java
@@ -60,29 +60,25 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
 
-    public final int netId;
     public final @EventType int eventType;
     public final long durationMs;
 
-    public NetworkEvent(int netId, @EventType int eventType, long durationMs) {
-        this.netId = netId;
+    public NetworkEvent(@EventType int eventType, long durationMs) {
         this.eventType = eventType;
         this.durationMs = durationMs;
     }
 
-    public NetworkEvent(int netId, @EventType int eventType) {
-        this(netId, eventType, 0);
+    public NetworkEvent(@EventType int eventType) {
+        this(eventType, 0);
     }
 
     private NetworkEvent(Parcel in) {
-        netId = in.readInt();
         eventType = in.readInt();
         durationMs = in.readLong();
     }
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(netId);
         out.writeInt(eventType);
         out.writeLong(durationMs);
     }
@@ -105,8 +101,8 @@
 
     @Override
     public String toString() {
-        return String.format("NetworkEvent(%d, %s, %dms)",
-                netId, Decoder.constants.get(eventType), durationMs);
+        return String.format("NetworkEvent(%s, %dms)",
+                Decoder.constants.get(eventType), durationMs);
     }
 
     final static class Decoder {
diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java
index cbf3fc8..8f1a5c4 100644
--- a/android/net/metrics/WakeupEvent.java
+++ b/android/net/metrics/WakeupEvent.java
@@ -16,6 +16,10 @@
 
 package android.net.metrics;
 
+import android.net.MacAddress;
+
+import java.util.StringJoiner;
+
 /**
  * An event logged when NFLOG notifies userspace of a wakeup packet for
  * watched interfaces.
@@ -23,12 +27,35 @@
  */
 public class WakeupEvent {
     public String iface;
-    public long timestampMs;
     public int uid;
+    public int ethertype;
+    public byte[] dstHwAddr;
+    public String srcIp;
+    public String dstIp;
+    public int ipNextHeader;
+    public int srcPort;
+    public int dstPort;
+    public long timestampMs;
 
     @Override
     public String toString() {
-        return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)",
-                timestampMs, timestampMs, iface, uid);
+        StringJoiner j = new StringJoiner(", ", "WakeupEvent(", ")");
+        j.add(String.format("%tT.%tL", timestampMs, timestampMs));
+        j.add(iface);
+        j.add("uid: " + Integer.toString(uid));
+        j.add("eth=0x" + Integer.toHexString(ethertype));
+        j.add("dstHw=" + MacAddress.stringAddrFromByteAddr(dstHwAddr));
+        if (ipNextHeader > 0) {
+            j.add("ipNxtHdr=" + ipNextHeader);
+            j.add("srcIp=" + srcIp);
+            j.add("dstIp=" + dstIp);
+            if (srcPort > -1) {
+                j.add("srcPort=" + srcPort);
+            }
+            if (dstPort > -1) {
+                j.add("dstPort=" + dstPort);
+            }
+        }
+        return j.toString();
     }
 }
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
index 97e83f9..1ba9777 100644
--- a/android/net/metrics/WakeupStats.java
+++ b/android/net/metrics/WakeupStats.java
@@ -16,8 +16,12 @@
 
 package android.net.metrics;
 
+import android.net.MacAddress;
 import android.os.Process;
 import android.os.SystemClock;
+import android.util.SparseIntArray;
+
+import java.util.StringJoiner;
 
 /**
  * An event logged per interface and that aggregates WakeupEvents for that interface.
@@ -38,6 +42,13 @@
     public long noUidWakeups = 0;
     public long durationSec = 0;
 
+    public long l2UnicastCount = 0;
+    public long l2MulticastCount = 0;
+    public long l2BroadcastCount = 0;
+
+    public final SparseIntArray ethertypes = new SparseIntArray();
+    public final SparseIntArray ipNextHeaders = new SparseIntArray();
+
     public WakeupStats(String iface) {
         this.iface = iface;
     }
@@ -68,20 +79,56 @@
                 }
                 break;
         }
+
+        switch (MacAddress.macAddressType(ev.dstHwAddr)) {
+            case UNICAST:
+                l2UnicastCount++;
+                break;
+            case MULTICAST:
+                l2MulticastCount++;
+                break;
+            case BROADCAST:
+                l2BroadcastCount++;
+                break;
+            default:
+                break;
+        }
+
+        increment(ethertypes, ev.ethertype);
+        if (ev.ipNextHeader >= 0) {
+            increment(ipNextHeaders, ev.ipNextHeader);
+        }
     }
 
     @Override
     public String toString() {
         updateDuration();
-        return new StringBuilder()
-                .append("WakeupStats(").append(iface)
-                .append(", total: ").append(totalWakeups)
-                .append(", root: ").append(rootWakeups)
-                .append(", system: ").append(systemWakeups)
-                .append(", apps: ").append(applicationWakeups)
-                .append(", non-apps: ").append(nonApplicationWakeups)
-                .append(", no uid: ").append(noUidWakeups)
-                .append(", ").append(durationSec).append("s)")
-                .toString();
+        StringJoiner j = new StringJoiner(", ", "WakeupStats(", ")");
+        j.add(iface);
+        j.add("" + durationSec + "s");
+        j.add("total: " + totalWakeups);
+        j.add("root: " + rootWakeups);
+        j.add("system: " + systemWakeups);
+        j.add("apps: " + applicationWakeups);
+        j.add("non-apps: " + nonApplicationWakeups);
+        j.add("no uid: " + noUidWakeups);
+        j.add(String.format("l2 unicast/multicast/broadcast: %d/%d/%d",
+                l2UnicastCount, l2MulticastCount, l2BroadcastCount));
+        for (int i = 0; i < ethertypes.size(); i++) {
+            int eth = ethertypes.keyAt(i);
+            int count = ethertypes.valueAt(i);
+            j.add(String.format("ethertype 0x%x: %d", eth, count));
+        }
+        for (int i = 0; i < ipNextHeaders.size(); i++) {
+            int proto = ipNextHeaders.keyAt(i);
+            int count = ipNextHeaders.valueAt(i);
+            j.add(String.format("ipNxtHdr %d: %d", proto, count));
+        }
+        return j.toString();
+    }
+
+    private static void increment(SparseIntArray counters, int key) {
+        int newcount = counters.get(key, 0) + 1;
+        counters.put(key, newcount);
     }
 }
diff --git a/android/net/wifi/WifiInfo.java b/android/net/wifi/WifiInfo.java
index a367b23..bf8fed1 100644
--- a/android/net/wifi/WifiInfo.java
+++ b/android/net/wifi/WifiInfo.java
@@ -348,6 +348,9 @@
      * quotation marks. Otherwise, it is returned as a string of hex digits. The
      * SSID may be &lt;unknown ssid&gt; if there is no network currently connected,
      * or if the caller has insufficient permissions to access the SSID.
+     *
+     * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method
+     * always returned the SSID with no quotes around it.
      * @return the SSID
      */
     public String getSSID() {
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index c2959d5..66fabf3 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -1127,7 +1128,7 @@
      */
     private int addOrUpdateNetwork(WifiConfiguration config) {
         try {
-            return mService.addOrUpdateNetwork(config);
+            return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1148,7 +1149,7 @@
      */
     public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
-            if (!mService.addOrUpdatePasspointConfiguration(config)) {
+            if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
                 throw new IllegalArgumentException();
             }
         } catch (RemoteException e) {
@@ -1165,7 +1166,7 @@
      */
     public void removePasspointConfiguration(String fqdn) {
         try {
-            if (!mService.removePasspointConfiguration(fqdn)) {
+            if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
                 throw new IllegalArgumentException();
             }
         } catch (RemoteException e) {
@@ -1251,7 +1252,7 @@
      */
     public boolean removeNetwork(int netId) {
         try {
-            return mService.removeNetwork(netId);
+            return mService.removeNetwork(netId, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1297,7 +1298,7 @@
 
         boolean success;
         try {
-            success = mService.enableNetwork(netId, attemptConnect);
+            success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1323,7 +1324,7 @@
      */
     public boolean disableNetwork(int netId) {
         try {
-            return mService.disableNetwork(netId);
+            return mService.disableNetwork(netId, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1336,7 +1337,7 @@
      */
     public boolean disconnect() {
         try {
-            mService.disconnect();
+            mService.disconnect(mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1351,7 +1352,7 @@
      */
     public boolean reconnect() {
         try {
-            mService.reconnect();
+            mService.reconnect(mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1366,7 +1367,7 @@
      */
     public boolean reassociate() {
         try {
-            mService.reassociate();
+            mService.reassociate(mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1739,7 +1740,7 @@
     @Deprecated
     public boolean saveConfiguration() {
         try {
-            return mService.saveConfiguration();
+            return mService.saveConfiguration(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1748,13 +1749,12 @@
     /**
      * Set the country code.
      * @param countryCode country code in ISO 3166 format.
-     * @param persist {@code true} if this needs to be remembered
      *
      * @hide
      */
-    public void setCountryCode(String country, boolean persist) {
+    public void setCountryCode(@NonNull String country) {
         try {
-            mService.setCountryCode(country, persist);
+            mService.setCountryCode(country);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1803,18 +1803,14 @@
 
     /**
      * Enable or disable Wi-Fi.
-     *
-     * Note: This method will return false if wifi cannot be enabled (e.g., an incompatible mode
-     * where the user has enabled tethering or Airplane Mode).
-     *
-     * Applications need to have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
-     * permission to toggle wifi. Callers without the permissions will trigger a
-     * {@link java.lang.SecurityException}.
+     * <p>
+     * Applications must have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+     * permission to toggle wifi.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
-     * @return {@code true} if the operation succeeds (or if the existing state
-     *         is the same as the requested state). False if wifi cannot be toggled on/off when the
-     *         request is made.
+     * @return {@code false} if the request cannot be satisfied; {@code true} indicates that wifi is
+     *         either already in the requested state, or in progress toward the requested state.
+     * @throws  {@link java.lang.SecurityException} if the caller is missing required permissions.
      */
     public boolean setWifiEnabled(boolean enabled) {
         try {
@@ -2060,7 +2056,7 @@
             }
             mLOHSCallbackProxy = null;
             try {
-                mService.stopLocalOnlyHotspot();
+                mService.stopLocalOnlyHotspot(mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -2179,7 +2175,7 @@
     @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
     public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
         try {
-            mService.setWifiApConfiguration(wifiConfig);
+            mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -2951,7 +2947,7 @@
     public void disableEphemeralNetwork(String SSID) {
         if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
         try {
-            mService.disableEphemeralNetwork(SSID);
+            mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2990,7 +2986,7 @@
      */
     public Messenger getWifiServiceMessenger() {
         try {
-            return mService.getWifiServiceMessenger();
+            return mService.getWifiServiceMessenger(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3441,6 +3437,7 @@
      * Set wifi verbose log. Called from developer settings.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void enableVerboseLogging (int verbose) {
         try {
             mService.enableVerboseLogging(verbose);
@@ -3519,7 +3516,7 @@
      */
     public void factoryReset() {
         try {
-            mService.factoryReset();
+            mService.factoryReset(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3546,7 +3543,7 @@
      */
     public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
         try {
-            return mService.setEnableAutoJoinWhenAssociated(enabled);
+            return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
index 7405e82..c8aea3c 100644
--- a/android/net/wifi/rtt/RangingResultCallback.java
+++ b/android/net/wifi/rtt/RangingResultCallback.java
@@ -36,7 +36,7 @@
  */
 public abstract class RangingResultCallback {
     /** @hide */
-    @IntDef({STATUS_CODE_FAIL})
+    @IntDef({STATUS_CODE_FAIL, STATUS_CODE_FAIL_RTT_NOT_AVAILABLE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface RangingOperationStatus {
     }
@@ -47,6 +47,14 @@
     public static final int STATUS_CODE_FAIL = 1;
 
     /**
+     * A failure code for the whole ranging request operation. Indicates that the request failed due
+     * to RTT not being available - e.g. Wi-Fi was disabled. Use the
+     * {@link WifiRttManager#isAvailable()} and {@link WifiRttManager#ACTION_WIFI_RTT_STATE_CHANGED}
+     * to track RTT availability.
+     */
+    public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2;
+
+    /**
      * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
      * devices specified in the request was attempted.
      *
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
index 435bb37..128d6c9 100644
--- a/android/net/wifi/rtt/WifiRttManager.java
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -3,15 +3,19 @@
 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 static android.Manifest.permission.LOCATION_HARDWARE;
 
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 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.os.WorkSource;
 import android.util.Log;
 
 import java.util.List;
@@ -22,11 +26,18 @@
  * <p>
  * The devices which can be ranged include:
  * <li>Access Points (APs)
+ * <li>Wi-Fi Aware peers
  * <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.
+ * <p>
+ *     Wi-Fi RTT may not be usable at some points, e.g. when Wi-Fi is disabled. To validate that
+ *     the functionality is available use the {@link #isAvailable()} function. To track
+ *     changes in RTT usability register for the {@link #ACTION_WIFI_RTT_STATE_CHANGED}
+ *     broadcast. Note that this broadcast is not sticky - you should register for it and then
+ *     check the above API to avoid a race condition.
  *
  * @hide RTT_API
  */
@@ -38,6 +49,18 @@
     private final Context mContext;
     private final IWifiRttManager mService;
 
+    /**
+     * Broadcast intent action to indicate that the state of Wi-Fi RTT availability has changed.
+     * Use the {@link #isAvailable()} to query the current status.
+     * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
+     * the broadcast to check the current state of Wi-Fi RTT.
+     * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+     * components will be launched.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WIFI_RTT_STATE_CHANGED =
+            "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
+
     /** @hide */
     public WifiRttManager(Context context, IWifiRttManager service) {
         mContext = context;
@@ -45,6 +68,22 @@
     }
 
     /**
+     * Returns the current status of RTT API: whether or not RTT is available. To track
+     * changes in the state of RTT API register for the
+     * {@link #ACTION_WIFI_RTT_STATE_CHANGED} broadcast.
+     *
+     * @return A boolean indicating whether the app can use the RTT API at this time (true) or
+     * not (false).
+     */
+    public boolean isAvailable() {
+        try {
+            return mService.isAvailable();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * 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.
      *
@@ -58,21 +97,63 @@
     @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
     public void startRanging(RangingRequest request, RangingResultCallback callback,
             @Nullable Handler handler) {
+        startRanging(null, request, callback, handler);
+    }
+
+    /**
+     * 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 workSource A mechanism to specify an alternative work-source for the request.
+     * @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.
+     *
+     * @hide (@SystemApi)
+     */
+    @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE,
+            ACCESS_WIFI_STATE})
+    public void startRanging(@Nullable WorkSource workSource, RangingRequest request,
+            RangingResultCallback callback, @Nullable Handler handler) {
         if (VDBG) {
-            Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler="
-                    + handler);
+            Log.v(TAG, "startRanging: workSource=" + workSource + ", 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,
+            mService.startRanging(binder, mContext.getOpPackageName(), workSource, request,
                     new RttCallbackProxy(looper, callback));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /**
+     * Cancel all ranging requests for the specified work sources. The requests have been requested
+     * using {@link #startRanging(WorkSource, RangingRequest, RangingResultCallback, Handler)}.
+     *
+     * @param workSource The work-sources of the requesters.
+     *
+     * @hide (@SystemApi)
+     */
+    @RequiresPermission(allOf = {LOCATION_HARDWARE})
+    public void cancelRanging(WorkSource workSource) {
+        if (VDBG) {
+            Log.v(TAG, "cancelRanging: workSource=" + workSource);
+        }
+
+        try {
+            mService.cancelRanging(workSource);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private static class RttCallbackProxy extends IRttCallback.Stub {
         private final Handler mHandler;
         private final RangingResultCallback mCallback;
diff --git a/android/os/BatteryManager.java b/android/os/BatteryManager.java
index f715f50..6e0f70c 100644
--- a/android/os/BatteryManager.java
+++ b/android/os/BatteryManager.java
@@ -19,6 +19,7 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.hardware.health.V1_0.Constants;
+
 import com.android.internal.app.IBatteryStats;
 
 /**
@@ -33,39 +34,39 @@
      * integer containing the current status constant.
      */
     public static final String EXTRA_STATUS = "status";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer containing the current health constant.
      */
     public static final String EXTRA_HEALTH = "health";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * boolean indicating whether a battery is present.
      */
     public static final String EXTRA_PRESENT = "present";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer field containing the current battery level, from 0 to
      * {@link #EXTRA_SCALE}.
      */
     public static final String EXTRA_LEVEL = "level";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer containing the maximum battery level.
      */
     public static final String EXTRA_SCALE = "scale";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer containing the resource ID of a small status bar icon
      * indicating the current battery state.
      */
     public static final String EXTRA_ICON_SMALL = "icon-small";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer indicating whether the device is plugged in to a power
@@ -73,19 +74,19 @@
      * types of power sources.
      */
     public static final String EXTRA_PLUGGED = "plugged";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer containing the current battery voltage level.
      */
     public static final String EXTRA_VOLTAGE = "voltage";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer containing the current battery temperature.
      */
     public static final String EXTRA_TEMPERATURE = "temperature";
-    
+
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * String describing the technology of the current battery.
@@ -216,6 +217,7 @@
      */
     public static final int BATTERY_PROPERTY_STATUS = 6;
 
+    private final Context mContext;
     private final IBatteryStats mBatteryStats;
     private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
 
@@ -223,6 +225,7 @@
      * @removed Was previously made visible by accident.
      */
     public BatteryManager() {
+        mContext = null;
         mBatteryStats = IBatteryStats.Stub.asInterface(
                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
         mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
@@ -230,8 +233,10 @@
     }
 
     /** {@hide} */
-    public BatteryManager(IBatteryStats batteryStats,
+    public BatteryManager(Context context,
+            IBatteryStats batteryStats,
             IBatteryPropertiesRegistrar batteryPropertiesRegistrar) {
+        mContext = context;
         mBatteryStats = batteryStats;
         mBatteryPropertiesRegistrar = batteryPropertiesRegistrar;
     }
@@ -278,16 +283,23 @@
     }
 
     /**
-     * Return the value of a battery property of integer type.  If the
-     * platform does not provide the property queried, this value will
-     * be Integer.MIN_VALUE.
+     * Return the value of a battery property of integer type.
      *
      * @param id identifier of the requested property
      *
-     * @return the property value, or Integer.MIN_VALUE if not supported.
+     * @return the property value. If the property is not supported or there is any other error,
+     *    return (a) 0 if {@code targetSdkVersion < VERSION_CODES.P} or (b) Integer.MIN_VALUE
+     *    if {@code targetSdkVersion >= VERSION_CODES.P}.
      */
     public int getIntProperty(int id) {
-        return (int)queryProperty(id);
+        long value = queryProperty(id);
+        if (value == Long.MIN_VALUE && mContext != null
+                && mContext.getApplicationInfo().targetSdkVersion
+                    >= android.os.Build.VERSION_CODES.P) {
+            return Integer.MIN_VALUE;
+        }
+
+        return (int) value;
     }
 
     /**
diff --git a/android/os/BatteryStatsInternal.java b/android/os/BatteryStatsInternal.java
new file mode 100644
index 0000000..b0436eb
--- /dev/null
+++ b/android/os/BatteryStatsInternal.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Battery stats local system service interface. This is used to pass internal data out of
+ * BatteryStatsImpl.
+ *
+ * @hide Only for use within Android OS.
+ */
+public abstract class BatteryStatsInternal {
+    /**
+     * Returns the wifi interfaces.
+     */
+    public abstract String[] getWifiIfaces();
+
+    /**
+     * Returns the mobile data interfaces.
+     */
+    public abstract String[] getMobileIfaces();
+}
diff --git a/android/os/Binder.java b/android/os/Binder.java
index 2bfb013..b5bcd02 100644
--- a/android/os/Binder.java
+++ b/android/os/Binder.java
@@ -193,6 +193,19 @@
     }
 
     /**
+     * Reset the given interface back to the default blocking behavior,
+     * reverting any changes made by {@link #allowBlocking(IBinder)}.
+     *
+     * @hide
+     */
+    public static IBinder defaultBlocking(IBinder binder) {
+        if (binder instanceof BinderProxy) {
+            ((BinderProxy) binder).mWarnOnBlocking = sWarnOnBlocking;
+        }
+        return binder;
+    }
+
+    /**
      * Inherit the current {@link #allowBlocking(IBinder)} value from one given
      * interface to another.
      *
diff --git a/android/os/Binder_Delegate.java b/android/os/Binder_Delegate.java
new file mode 100644
index 0000000..03596de
--- /dev/null
+++ b/android/os/Binder_Delegate.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.
+ */
+
+package android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate overriding selected methods of android.os.Binder
+ *
+ * Through the layoutlib_create tool, selected methods of Binder have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Binder_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Binder_Delegate> sManager =
+            new DelegateManager<>(Binder_Delegate.class);
+    private static long sFinalizer = -1;
+
+    @LayoutlibDelegate
+    /*package*/ static long getNativeBBinderHolder() {
+        return sManager.addNewDelegate(new Binder_Delegate());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long getNativeFinalizer() {
+        synchronized (Binder_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+                        sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+}
diff --git a/android/os/ConfigUpdate.java b/android/os/ConfigUpdate.java
index 1396877..94a44ec 100644
--- a/android/os/ConfigUpdate.java
+++ b/android/os/ConfigUpdate.java
@@ -68,13 +68,6 @@
             = "android.intent.action.UPDATE_CT_LOGS";
 
     /**
-     * Update system wide timezone data.
-     * @hide
-     */
-    @SystemApi
-    public static final String ACTION_UPDATE_TZDATA = "android.intent.action.UPDATE_TZDATA";
-
-    /**
      * Update language detection model file.
      * @hide
      */
diff --git a/android/os/Debug.java b/android/os/Debug.java
index 017c213..2acf36f 100644
--- a/android/os/Debug.java
+++ b/android/os/Debug.java
@@ -16,14 +16,16 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.util.Log;
 
 import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.TypedProperties;
 
-import dalvik.bytecode.OpcodeInfo;
 import dalvik.system.VMDebug;
 
 import org.apache.harmony.dalvik.ddmc.Chunk;
@@ -48,8 +50,6 @@
 import java.util.Map;
 
 
-
-
 /**
  * Provides various debugging methods for Android applications, including
  * tracing and allocation counts.
@@ -1959,13 +1959,7 @@
      */
     @Deprecated
     public static class InstructionCount {
-        private static final int NUM_INSTR =
-            OpcodeInfo.MAXIMUM_PACKED_VALUE + 1;
-
-        private int[] mCounts;
-
         public InstructionCount() {
-            mCounts = new int[NUM_INSTR];
         }
 
         /**
@@ -1975,13 +1969,7 @@
          * @return true if counting was started
          */
         public boolean resetAndStart() {
-            try {
-                VMDebug.startInstructionCounting();
-                VMDebug.resetInstructionCount();
-            } catch (UnsupportedOperationException uoe) {
-                return false;
-            }
-            return true;
+            return false;
         }
 
         /**
@@ -1989,13 +1977,7 @@
          * counting process.
          */
         public boolean collect() {
-            try {
-                VMDebug.stopInstructionCounting();
-                VMDebug.getInstructionCount(mCounts);
-            } catch (UnsupportedOperationException uoe) {
-                return false;
-            }
-            return true;
+            return false;
         }
 
         /**
@@ -2003,13 +1985,7 @@
          * all threads).
          */
         public int globalTotal() {
-            int count = 0;
-
-            for (int i = 0; i < NUM_INSTR; i++) {
-                count += mCounts[i];
-            }
-
-            return count;
+            return 0;
         }
 
         /**
@@ -2017,15 +1993,7 @@
          * executed globally.
          */
         public int globalMethodInvocations() {
-            int count = 0;
-
-            for (int i = 0; i < NUM_INSTR; i++) {
-                if (OpcodeInfo.isInvoke(i)) {
-                    count += mCounts[i];
-                }
-            }
-
-            return count;
+            return 0;
         }
     }
 
@@ -2382,4 +2350,24 @@
     public static String getCaller() {
         return getCaller(Thread.currentThread().getStackTrace(), 0);
     }
+
+    /**
+     * Attach a library as a jvmti agent to the current runtime.
+     *
+     * @param library library containing the agent
+     * @param options options passed to the agent
+     *
+     * @throws IOException If the agent could not be attached
+     */
+    public static void attachJvmtiAgent(@NonNull String library, @Nullable String options)
+            throws IOException {
+        Preconditions.checkNotNull(library);
+        Preconditions.checkArgument(!library.contains("="));
+
+        if (options == null) {
+            VMDebug.attachAgent(library);
+        } else {
+            VMDebug.attachAgent(library + "=" + options);
+        }
+    }
 }
diff --git a/android/os/Environment.java b/android/os/Environment.java
index 5b0e5bb..f977c1d 100644
--- a/android/os/Environment.java
+++ b/android/os/Environment.java
@@ -836,7 +836,6 @@
      *         physically removed.
      */
     public static boolean isExternalStorageRemovable() {
-        if (isStorageDisabled()) return false;
         final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageRemovable(externalDir);
     }
@@ -875,7 +874,6 @@
      *      boolean)
      */
     public static boolean isExternalStorageEmulated() {
-        if (isStorageDisabled()) return false;
         final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageEmulated(externalDir);
     }
@@ -951,9 +949,6 @@
         return cur;
     }
 
-    private static boolean isStorageDisabled() {
-        return SystemProperties.getBoolean("config.disable_storage", false);
-    }
 
     /**
      * If the given path exists on emulated external storage, return the
diff --git a/android/os/FileUtils.java b/android/os/FileUtils.java
index 56d6e0a..7c53ec1 100644
--- a/android/os/FileUtils.java
+++ b/android/os/FileUtils.java
@@ -320,8 +320,17 @@
      * is {@code filename}.
      */
     public static void bytesToFile(String filename, byte[] content) throws IOException {
-        try (FileOutputStream fos = new FileOutputStream(filename)) {
-            fos.write(content);
+        if (filename.startsWith("/proc/")) {
+            final int oldMask = StrictMode.allowThreadDiskWritesMask();
+            try (FileOutputStream fos = new FileOutputStream(filename)) {
+                fos.write(content);
+            } finally {
+                StrictMode.setThreadPolicyMask(oldMask);
+            }
+        } else {
+            try (FileOutputStream fos = new FileOutputStream(filename)) {
+                fos.write(content);
+            }
         }
     }
 
diff --git a/android/os/HidlSupport.java b/android/os/HidlSupport.java
index 7dec4d7..3544ea1 100644
--- a/android/os/HidlSupport.java
+++ b/android/os/HidlSupport.java
@@ -156,4 +156,27 @@
         // Should not reach here.
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Test that two interfaces are equal. This is the Java equivalent to C++
+     * interfacesEqual function.
+     * This essentially calls .equals on the internal binder objects (via Binder()).
+     * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder}
+     *   object, and they are compared in {@link HwRemoteBinder#equals}.
+     * - If both interfaces are stubs, asBinder() returns the object itself. By default,
+     *   auto-generated IFoo.Stub does not override equals(), but an implementation can
+     *   optionally override it, and {@code interfacesEqual} will use it here.
+     */
+    public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
+        if (lft == rgt) {
+            return true;
+        }
+        if (lft == null || rgt == null) {
+            return false;
+        }
+        if (!(rgt instanceof IHwInterface)) {
+            return false;
+        }
+        return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder());
+    }
 }
diff --git a/android/os/HwBinder.java b/android/os/HwBinder.java
index 270e63f..5e2a081 100644
--- a/android/os/HwBinder.java
+++ b/android/os/HwBinder.java
@@ -16,10 +16,10 @@
 
 package android.os;
 
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
 import libcore.util.NativeAllocationRegistry;
 
+import java.util.NoSuchElementException;
+
 /** @hide */
 public abstract class HwBinder implements IHwBinder {
     private static final String TAG = "HwBinder";
@@ -46,9 +46,16 @@
     public native final void registerService(String serviceName)
         throws RemoteException;
 
-    public static native final IHwBinder getService(
+    public static final IHwBinder getService(
             String iface,
             String serviceName)
+        throws RemoteException, NoSuchElementException {
+        return getService(iface, serviceName, false /* retry */);
+    }
+    public static native final IHwBinder getService(
+            String iface,
+            String serviceName,
+            boolean retry)
         throws RemoteException, NoSuchElementException;
 
     public static native final void configureRpcThreadpool(
diff --git a/android/os/HwBlob.java b/android/os/HwBlob.java
index 88226f0..5e9b9ae 100644
--- a/android/os/HwBlob.java
+++ b/android/os/HwBlob.java
@@ -43,6 +43,18 @@
     public native final double getDouble(long offset);
     public native final String getString(long offset);
 
+    /**
+      The copyTo... methods copy the blob's data, starting from the given
+      byte offset, into the array. A total of "size" _elements_ are copied.
+     */
+    public native final void copyToBoolArray(long offset, boolean[] array, int size);
+    public native final void copyToInt8Array(long offset, byte[] array, int size);
+    public native final void copyToInt16Array(long offset, short[] array, int size);
+    public native final void copyToInt32Array(long offset, int[] array, int size);
+    public native final void copyToInt64Array(long offset, long[] array, int size);
+    public native final void copyToFloatArray(long offset, float[] array, int size);
+    public native final void copyToDoubleArray(long offset, double[] array, int size);
+
     public native final void putBool(long offset, boolean x);
     public native final void putInt8(long offset, byte x);
     public native final void putInt16(long offset, short x);
@@ -52,6 +64,14 @@
     public native final void putDouble(long offset, double x);
     public native final void putString(long offset, String x);
 
+    public native final void putBoolArray(long offset, boolean[] x);
+    public native final void putInt8Array(long offset, byte[] x);
+    public native final void putInt16Array(long offset, short[] x);
+    public native final void putInt32Array(long offset, int[] x);
+    public native final void putInt64Array(long offset, long[] x);
+    public native final void putFloatArray(long offset, float[] x);
+    public native final void putDoubleArray(long offset, double[] x);
+
     public native final void putBlob(long offset, HwBlob blob);
 
     public native final long handle();
diff --git a/android/os/HwRemoteBinder.java b/android/os/HwRemoteBinder.java
index 2f89ce6..a07e42c 100644
--- a/android/os/HwRemoteBinder.java
+++ b/android/os/HwRemoteBinder.java
@@ -63,4 +63,9 @@
     }
 
     private long mNativeContext;
+
+    @Override
+    public final native boolean equals(Object other);
+    @Override
+    public final native int hashCode();
 }
diff --git a/android/os/LocaleList.java b/android/os/LocaleList.java
index 2dc3beb..ca9cbec 100644
--- a/android/os/LocaleList.java
+++ b/android/os/LocaleList.java
@@ -295,7 +295,11 @@
         return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale);
     }
 
-    private static boolean isPseudoLocale(Locale locale) {
+    /**
+     * Returns true if locale is a pseudo-locale, false otherwise.
+     * {@hide}
+     */
+    public static boolean isPseudoLocale(Locale locale) {
         return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
     }
 
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index c2cf396..10adb5a 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -2020,8 +2020,6 @@
     @Deprecated
     static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
 
-    static native void clearFileDescriptor(FileDescriptor desc);
-
     /**
      * Read a byte value from the parcel at the current dataPosition().
      */
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
index 7f588ad..7556f09 100644
--- a/android/os/ParcelFileDescriptor.java
+++ b/android/os/ParcelFileDescriptor.java
@@ -683,7 +683,7 @@
                 throw new IllegalStateException("Already closed");
             }
             final int fd = getFd();
-            Parcel.clearFileDescriptor(mFd);
+            mFd.setInt$(-1);
             writeCommStatusAndClose(Status.DETACHED, null);
             mClosed = true;
             mGuard.close();
diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java
index 7f4dee6..dd4825e 100644
--- a/android/os/PowerManager.java
+++ b/android/os/PowerManager.java
@@ -513,6 +513,53 @@
      */
     public static final int SHUTDOWN_REASON_BATTERY_THERMAL = 6;
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ServiceType.GPS,
+            ServiceType.VIBRATION,
+            ServiceType.ANIMATION,
+            ServiceType.FULL_BACKUP,
+            ServiceType.KEYVALUE_BACKUP,
+            ServiceType.NETWORK_FIREWALL,
+            ServiceType.SCREEN_BRIGHTNESS,
+            ServiceType.SOUND,
+            ServiceType.BATTERY_STATS,
+            ServiceType.DATA_SAVER,
+            ServiceType.FORCE_ALL_APPS_STANDBY_JOBS,
+            ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS,
+            ServiceType.OPTIONAL_SENSORS,
+    })
+    public @interface ServiceType {
+        int NULL = 0;
+        int GPS = 1;
+        int VIBRATION = 2;
+        int ANIMATION = 3;
+        int FULL_BACKUP = 4;
+        int KEYVALUE_BACKUP = 5;
+        int NETWORK_FIREWALL = 6;
+        int SCREEN_BRIGHTNESS = 7;
+        int SOUND = 8;
+        int BATTERY_STATS = 9;
+        int DATA_SAVER = 10;
+
+        /**
+         * Whether the job scheduler should force app standby on all apps on battery saver or not.
+         */
+        int FORCE_ALL_APPS_STANDBY_JOBS = 11;
+
+        /**
+         * Whether the alarm manager should force app standby on all apps on battery saver or not.
+         */
+        int FORCE_ALL_APPS_STANDBY_ALARMS = 12;
+
+        /**
+         * Whether to disable non-essential sensors. (e.g. edge sensors.)
+         */
+        int OPTIONAL_SENSORS = 13;
+    }
+
     final Context mContext;
     final IPowerManager mService;
     final Handler mHandler;
@@ -1055,15 +1102,14 @@
 
     /**
      * Get data about the battery saver mode for a specific service
-     * @param serviceType unique key for the service, one of
-     *             {@link com.android.server.power.BatterySaverPolicy.ServiceType}
+     * @param serviceType unique key for the service, one of {@link ServiceType}
      * @return Battery saver state data.
      *
      * @hide
      * @see com.android.server.power.BatterySaverPolicy
      * @see PowerSaveState
      */
-    public PowerSaveState getPowerSaveState(int serviceType) {
+    public PowerSaveState getPowerSaveState(@ServiceType int serviceType) {
         try {
             return mService.getPowerSaveState(serviceType);
         } catch (RemoteException e) {
diff --git a/android/os/PowerManagerInternal.java b/android/os/PowerManagerInternal.java
index a01b8ed..77ac265 100644
--- a/android/os/PowerManagerInternal.java
+++ b/android/os/PowerManagerInternal.java
@@ -18,6 +18,8 @@
 
 import android.view.Display;
 
+import java.util.function.Consumer;
+
 /**
  * Power manager local system service interface.
  *
@@ -125,6 +127,23 @@
 
     public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
 
+    /**
+     * Same as {@link #registerLowPowerModeObserver} but can take a lambda.
+     */
+    public void registerLowPowerModeObserver(int serviceType, Consumer<PowerSaveState> listener) {
+        registerLowPowerModeObserver(new LowPowerModeListener() {
+            @Override
+            public int getServiceType() {
+                return serviceType;
+            }
+
+            @Override
+            public void onLowPowerModeChanged(PowerSaveState state) {
+                listener.accept(state);
+            }
+        });
+    }
+
     public interface LowPowerModeListener {
         int getServiceType();
         void onLowPowerModeChanged(PowerSaveState state);
diff --git a/android/os/PowerSaveState.java b/android/os/PowerSaveState.java
index 7058a1d..de1128d 100644
--- a/android/os/PowerSaveState.java
+++ b/android/os/PowerSaveState.java
@@ -27,7 +27,7 @@
     /**
      * Whether we should enable battery saver for this service.
      *
-     * @see com.android.server.power.BatterySaverPolicy.ServiceType
+     * @see com.android.server.power.BatterySaverPolicy
      */
     public final boolean batterySaverEnabled;
     /**
diff --git a/android/os/RemoteCallbackList.java b/android/os/RemoteCallbackList.java
index 2281fb6..b9b9a18 100644
--- a/android/os/RemoteCallbackList.java
+++ b/android/os/RemoteCallbackList.java
@@ -19,6 +19,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import java.io.PrintWriter;
 import java.util.function.Consumer;
 
 /**
@@ -399,6 +400,13 @@
         }
     }
 
+    /** @hide */
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size());
+        pw.print(prefix); pw.print("killed: "); pw.println(mKilled);
+        pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount);
+    }
+
     private void logExcessiveCallbacks() {
         final long size = mCallbacks.size();
         final long TOO_MANY = 3000;
diff --git a/android/os/ShellCallback.java b/android/os/ShellCallback.java
index e7fe697..ad9fbfb 100644
--- a/android/os/ShellCallback.java
+++ b/android/os/ShellCallback.java
@@ -35,8 +35,9 @@
     IShellCallback mShellCallback;
 
     class MyShellCallback extends IShellCallback.Stub {
-        public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
-            return onOpenOutputFile(path, seLinuxContext);
+        public ParcelFileDescriptor openFile(String path, String seLinuxContext,
+                String mode) {
+            return onOpenFile(path, seLinuxContext, mode);
         }
     }
 
@@ -48,23 +49,27 @@
     }
 
     /**
-     * Ask the shell to open a file for writing.  This will truncate the file if it
-     * already exists.  It will create the file if it doesn't exist.
+     * Ask the shell to open a file.  If opening for writing, will truncate the file if it
+     * already exists and will create the file if it doesn't exist.
      * @param path Path of the file to be opened/created.
      * @param seLinuxContext Optional SELinux context that must be allowed to have
      * access to the file; if null, nothing is required.
+     * @param mode Mode to open file in: "r" for input/reading an existing file,
+     * "r+" for reading/writing an existing file, "w" for output/writing a new file (either
+     * creating or truncating an existing one), "w+" for reading/writing a new file (either
+     * creating or truncating an existing one).
      */
-    public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
-        if (DEBUG) Log.d(TAG, "openOutputFile " + this + ": mLocal=" + mLocal
+    public ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode) {
+        if (DEBUG) Log.d(TAG, "openFile " + this + " mode=" + mode + ": mLocal=" + mLocal
                 + " mShellCallback=" + mShellCallback);
 
         if (mLocal) {
-            return onOpenOutputFile(path, seLinuxContext);
+            return onOpenFile(path, seLinuxContext, mode);
         }
 
         if (mShellCallback != null) {
             try {
-                return mShellCallback.openOutputFile(path, seLinuxContext);
+                return mShellCallback.openFile(path, seLinuxContext, mode);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failure opening " + path, e);
             }
@@ -72,7 +77,7 @@
         return null;
     }
 
-    public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+    public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, String mode) {
         return null;
     }
 
diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java
index 6223235..d75219f 100644
--- a/android/os/ShellCommand.java
+++ b/android/os/ShellCommand.java
@@ -226,10 +226,10 @@
      * Helper for just system services to ask the shell to open an output file.
      * @hide
      */
-    public ParcelFileDescriptor openOutputFileForSystem(String path) {
+    public ParcelFileDescriptor openFileForSystem(String path, String mode) {
         try {
-            ParcelFileDescriptor pfd = getShellCallback().openOutputFile(path,
-                    "u:r:system_server:s0");
+            ParcelFileDescriptor pfd = getShellCallback().openFile(path,
+                    "u:r:system_server:s0", mode);
             if (pfd != null) {
                 return pfd;
             }
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index ee3e5bc..f90604a 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.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
@@ -25,8 +26,26 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
+import android.os.strictmode.CleartextNetworkViolation;
+import android.os.strictmode.ContentUriWithoutPermissionViolation;
+import android.os.strictmode.CustomViolation;
+import android.os.strictmode.DiskReadViolation;
+import android.os.strictmode.DiskWriteViolation;
+import android.os.strictmode.FileUriExposedViolation;
+import android.os.strictmode.InstanceCountViolation;
+import android.os.strictmode.IntentReceiverLeakedViolation;
+import android.os.strictmode.LeakedClosableViolation;
+import android.os.strictmode.NetworkViolation;
+import android.os.strictmode.ResourceMismatchViolation;
+import android.os.strictmode.ServiceConnectionLeakedViolation;
+import android.os.strictmode.SqliteObjectLeakedViolation;
+import android.os.strictmode.UnbufferedIoViolation;
+import android.os.strictmode.UntaggedSocketViolation;
+import android.os.strictmode.Violation;
+import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Printer;
@@ -35,6 +54,7 @@
 import android.view.IWindowManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -53,6 +73,8 @@
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.HashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -140,6 +162,15 @@
      */
     private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
 
+    /**
+     * Quick feature-flag that can be used to disable the defaults provided by {@link
+     * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
+     */
+    private static final boolean DISABLE = false;
+
+    // Only apply VM penalties for the same violation at this interval.
+    private static final long MIN_VM_INTERVAL_MS = 1000;
+
     // Only log a duplicate stack trace to the logs every second.
     private static final long MIN_LOG_INTERVAL_MS = 1000;
 
@@ -156,36 +187,30 @@
     // Byte 1: Thread-policy
 
     /** @hide */
-    @TestApi
-    public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
+    @TestApi public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
 
     /** @hide */
-    @TestApi
-    public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
+    @TestApi public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
 
     /** @hide */
-    @TestApi
-    public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
+    @TestApi public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
 
     /**
      * For StrictMode.noteSlowCall()
      *
      * @hide
      */
-    @TestApi
-    public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
+    @TestApi public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
 
     /**
      * For StrictMode.noteResourceMismatch()
      *
      * @hide
      */
-    @TestApi
-    public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
+    @TestApi public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
 
     /** @hide */
-    @TestApi
-    public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
+    @TestApi public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
 
     private static final int ALL_THREAD_DETECT_BITS =
             DETECT_DISK_WRITE
@@ -202,48 +227,40 @@
      *
      * @hide
      */
-    @TestApi
-    public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
+    @TestApi public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
 
     /**
      * Note, a "VM_" bit, not thread.
      *
      * @hide
      */
-    @TestApi
-    public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
+    @TestApi public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
 
     /**
      * Note, a "VM_" bit, not thread.
      *
      * @hide
      */
-    @TestApi
-    public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
+    @TestApi public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
 
     /** @hide */
-    @TestApi
-    public 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
+    @TestApi public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
 
     /** @hide */
-    @TestApi
-    public 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 */
-    @TestApi
-    public 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 */
     @TestApi
     public static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
 
     /** @hide */
-    @TestApi
-    public 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
@@ -354,15 +371,33 @@
                 } else {
                     msg = "StrictMode policy violation:";
                 }
-                if (info.hasStackTrace()) {
-                    Log.d(TAG, msg + " " + info.getStackTrace());
-                } else {
-                    Log.d(TAG, msg + " missing stack trace!");
-                }
+                Log.d(TAG, msg + " " + info.getStackTrace());
             };
 
     private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
 
+    private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
+            new ThreadLocal<>();
+    private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();
+
+    /**
+     * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+     * provided executor when a Thread violation occurs.
+     */
+    public interface OnThreadViolationListener {
+        /** Called on a thread policy violation. */
+        void onThreadViolation(Violation v);
+    }
+
+    /**
+     * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+     * provided executor when a VM violation occurs.
+     */
+    public interface OnVmViolationListener {
+        /** Called on a VM policy violation. */
+        void onVmViolation(Violation v);
+    }
+
     /** {@hide} */
     @TestApi
     public static void setViolationLogger(ViolationLogger listener) {
@@ -392,12 +427,16 @@
      */
     public static final class ThreadPolicy {
         /** The default, lax policy which doesn't catch anything. */
-        public static final ThreadPolicy LAX = new ThreadPolicy(0);
+        public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
 
         final int mask;
+        final OnThreadViolationListener mListener;
+        final Executor mCallbackExecutor;
 
-        private ThreadPolicy(int mask) {
+        private ThreadPolicy(int mask, OnThreadViolationListener listener, Executor executor) {
             this.mask = mask;
+            mListener = listener;
+            mCallbackExecutor = executor;
         }
 
         @Override
@@ -425,6 +464,8 @@
          */
         public static final class Builder {
             private int mMask = 0;
+            private OnThreadViolationListener mListener;
+            private Executor mExecutor;
 
             /**
              * Create a Builder that detects nothing and has no violations. (but note that {@link
@@ -590,6 +631,20 @@
                 return enable(PENALTY_DROPBOX);
             }
 
+            /**
+             * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+             * executor every violation.
+             */
+            public Builder penaltyListener(
+                    @NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
+                if (executor == null) {
+                    throw new NullPointerException("executor must not be null");
+                }
+                mListener = listener;
+                mExecutor = executor;
+                return this;
+            }
+
             private Builder enable(int bit) {
                 mMask |= bit;
                 return this;
@@ -609,7 +664,8 @@
             public ThreadPolicy build() {
                 // If there are detection bits set but no violation bits
                 // set, enable simple logging.
-                if (mMask != 0
+                if (mListener == null
+                        && mMask != 0
                         && (mMask
                                         & (PENALTY_DEATH
                                                 | PENALTY_LOG
@@ -618,7 +674,7 @@
                                 == 0) {
                     penaltyLog();
                 }
-                return new ThreadPolicy(mMask);
+                return new ThreadPolicy(mMask, mListener, mExecutor);
             }
         }
     }
@@ -630,19 +686,27 @@
      */
     public static final class VmPolicy {
         /** The default, lax policy which doesn't catch anything. */
-        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
+        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
 
         final int mask;
+        final OnVmViolationListener mListener;
+        final Executor mCallbackExecutor;
 
         // Map from class to max number of allowed instances in memory.
         final HashMap<Class, Integer> classInstanceLimit;
 
-        private VmPolicy(int mask, HashMap<Class, Integer> classInstanceLimit) {
+        private VmPolicy(
+                int mask,
+                HashMap<Class, Integer> classInstanceLimit,
+                OnVmViolationListener listener,
+                Executor executor) {
             if (classInstanceLimit == null) {
                 throw new NullPointerException("classInstanceLimit == null");
             }
             this.mask = mask;
             this.classInstanceLimit = classInstanceLimit;
+            mListener = listener;
+            mCallbackExecutor = executor;
         }
 
         @Override
@@ -670,6 +734,8 @@
          */
         public static final class Builder {
             private int mMask;
+            private OnVmViolationListener mListener;
+            private Executor mExecutor;
 
             private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
             private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
@@ -683,6 +749,8 @@
                 mMask = base.mask;
                 mClassInstanceLimitNeedCow = true;
                 mClassInstanceLimit = base.classInstanceLimit;
+                mListener = base.mListener;
+                mExecutor = base.mCallbackExecutor;
             }
 
             /**
@@ -714,6 +782,11 @@
                 return enable(DETECT_VM_ACTIVITY_LEAKS);
             }
 
+            /** @hide */
+            public Builder permitActivityLeaks() {
+                return disable(DETECT_VM_ACTIVITY_LEAKS);
+            }
+
             /**
              * Detect everything that's potentially suspect.
              *
@@ -847,6 +920,11 @@
                 return enable(DETECT_VM_UNTAGGED_SOCKET);
             }
 
+            /** @hide */
+            public Builder permitUntaggedSockets() {
+                return disable(DETECT_VM_UNTAGGED_SOCKET);
+            }
+
             /**
              * Crashes the whole process on violation. This penalty runs at the end of all enabled
              * penalties so you'll still get your logging or other violations before the process
@@ -889,6 +967,19 @@
                 return enable(PENALTY_DROPBOX);
             }
 
+            /**
+             * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+             */
+            public Builder penaltyListener(
+                    @NonNull OnVmViolationListener listener, @NonNull Executor executor) {
+                if (executor == null) {
+                    throw new NullPointerException("executor must not be null");
+                }
+                mListener = listener;
+                mExecutor = executor;
+                return this;
+            }
+
             private Builder enable(int bit) {
                 mMask |= bit;
                 return this;
@@ -908,7 +999,8 @@
             public VmPolicy build() {
                 // If there are detection bits set but no violation bits
                 // set, enable simple logging.
-                if (mMask != 0
+                if (mListener == null
+                        && mMask != 0
                         && (mMask
                                         & (PENALTY_DEATH
                                                 | PENALTY_LOG
@@ -919,7 +1011,9 @@
                 }
                 return new VmPolicy(
                         mMask,
-                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
+                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
+                        mListener,
+                        mExecutor);
             }
         }
     }
@@ -952,9 +1046,12 @@
      */
     public static void setThreadPolicy(final ThreadPolicy policy) {
         setThreadPolicyMask(policy.mask);
+        sThreadViolationListener.set(policy.mListener);
+        sThreadViolationExecutor.set(policy.mCallbackExecutor);
     }
 
-    private static void setThreadPolicyMask(final int policyMask) {
+    /** @hide */
+    public static void setThreadPolicyMask(final int policyMask) {
         // In addition to the Java-level thread-local in Dalvik's
         // BlockGuard, we also need to keep a native thread-local in
         // Binder in order to propagate the value across Binder calls,
@@ -991,55 +1088,6 @@
         CloseGuard.setEnabled(enabled);
     }
 
-    /** @hide */
-    public static class StrictModeViolation extends BlockGuard.BlockGuardPolicyException {
-        public StrictModeViolation(int policyState, int policyViolated, String message) {
-            super(policyState, policyViolated, message);
-        }
-    }
-
-    /** @hide */
-    public static class StrictModeNetworkViolation extends StrictModeViolation {
-        public StrictModeNetworkViolation(int policyMask) {
-            super(policyMask, DETECT_NETWORK, null);
-        }
-    }
-
-    /** @hide */
-    private static class StrictModeDiskReadViolation extends StrictModeViolation {
-        public StrictModeDiskReadViolation(int policyMask) {
-            super(policyMask, DETECT_DISK_READ, null);
-        }
-    }
-
-    /** @hide */
-    private static class StrictModeDiskWriteViolation extends StrictModeViolation {
-        public StrictModeDiskWriteViolation(int policyMask) {
-            super(policyMask, DETECT_DISK_WRITE, null);
-        }
-    }
-
-    /** @hide */
-    private static class StrictModeCustomViolation extends StrictModeViolation {
-        public StrictModeCustomViolation(int policyMask, String name) {
-            super(policyMask, DETECT_CUSTOM, name);
-        }
-    }
-
-    /** @hide */
-    private static class StrictModeResourceMismatchViolation extends StrictModeViolation {
-        public StrictModeResourceMismatchViolation(int policyMask, Object tag) {
-            super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null);
-        }
-    }
-
-    /** @hide */
-    private static class StrictModeUnbufferedIOViolation extends StrictModeViolation {
-        public StrictModeUnbufferedIOViolation(int policyMask) {
-            super(policyMask, DETECT_UNBUFFERED_IO, null);
-        }
-    }
-
     /**
      * Returns the bitmask of the current thread's policy.
      *
@@ -1056,7 +1104,10 @@
         // introduce VmPolicy cleanly) but this isn't particularly
         // optimal for users who might call this method often.  This
         // should be in a thread-local and not allocate on each call.
-        return new ThreadPolicy(getThreadPolicyMask());
+        return new ThreadPolicy(
+                getThreadPolicyMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
     }
 
     /**
@@ -1069,12 +1120,20 @@
      *     end of a block
      */
     public static ThreadPolicy allowThreadDiskWrites() {
+        return new ThreadPolicy(
+                allowThreadDiskWritesMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
+    }
+
+    /** @hide */
+    public static int allowThreadDiskWritesMask() {
         int oldPolicyMask = getThreadPolicyMask();
         int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_WRITE | DETECT_DISK_READ);
         if (newPolicyMask != oldPolicyMask) {
             setThreadPolicyMask(newPolicyMask);
         }
-        return new ThreadPolicy(oldPolicyMask);
+        return oldPolicyMask;
     }
 
     /**
@@ -1085,31 +1144,66 @@
      * @return the old policy, to be passed to setThreadPolicy to restore the policy.
      */
     public static ThreadPolicy allowThreadDiskReads() {
+        return new ThreadPolicy(
+                allowThreadDiskReadsMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
+    }
+
+    /** @hide */
+    public static int allowThreadDiskReadsMask() {
         int oldPolicyMask = getThreadPolicyMask();
         int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_READ);
         if (newPolicyMask != oldPolicyMask) {
             setThreadPolicyMask(newPolicyMask);
         }
-        return new ThreadPolicy(oldPolicyMask);
+        return oldPolicyMask;
     }
 
-    // We don't want to flash the screen red in the system server
-    // process, nor do we want to modify all the call sites of
-    // conditionallyEnableDebugLogging() in the system server,
-    // so instead we use this to determine if we are the system server.
-    private static boolean amTheSystemServerProcess() {
-        // Fast path.  Most apps don't have the system server's UID.
-        if (Process.myUid() != Process.SYSTEM_UID) {
-            return false;
-        }
+    private static ThreadPolicy allowThreadViolations() {
+        ThreadPolicy oldPolicy = getThreadPolicy();
+        setThreadPolicyMask(0);
+        return oldPolicy;
+    }
 
-        // The settings app, though, has the system server's UID so
-        // look up our stack to see if we came from the system server.
-        Throwable stack = new Throwable();
-        stack.fillInStackTrace();
-        for (StackTraceElement ste : stack.getStackTrace()) {
-            String clsName = ste.getClassName();
-            if (clsName != null && clsName.startsWith("com.android.server.")) {
+    private static VmPolicy allowVmViolations() {
+        VmPolicy oldPolicy = getVmPolicy();
+        sVmPolicy = VmPolicy.LAX;
+        return oldPolicy;
+    }
+
+    /**
+     * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+     * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
+     * chase any {@link StrictMode} regressions by enabling detection when running on {@link
+     * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
+     *
+     * <p>Unbundled apps included in the system image are expected to detect and triage their own
+     * {@link StrictMode} issues separate from the OS release process, which is why we don't enable
+     * them here.
+     *
+     * @hide
+     */
+    public static boolean isBundledSystemApp(ApplicationInfo ai) {
+        if (ai == null || ai.packageName == null) {
+            // Probably system server
+            return true;
+        } else if (ai.isSystemApp()) {
+            // Ignore unbundled apps living in the wrong namespace
+            if (ai.packageName.equals("com.android.vending")
+                    || ai.packageName.equals("com.android.chrome")) {
+                return false;
+            }
+
+            // Ignore bundled apps that are way too spammy
+            // STOPSHIP: burn this list down to zero
+            if (ai.packageName.equals("com.android.phone")) {
+                return false;
+            }
+
+            if (ai.packageName.equals("android")
+                    || ai.packageName.startsWith("android.")
+                    || ai.packageName.startsWith("com.android.")) {
                 return true;
             }
         }
@@ -1117,81 +1211,81 @@
     }
 
     /**
-     * Enable DropBox logging for debug phone builds.
+     * Initialize default {@link ThreadPolicy} for the current thread.
      *
      * @hide
      */
-    public static boolean conditionallyEnableDebugLogging() {
-        boolean doFlashes =
-                SystemProperties.getBoolean(VISUAL_PROPERTY, false) && !amTheSystemServerProcess();
-        final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false);
+    public static void initThreadDefaults(ApplicationInfo ai) {
+        final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
+        final int targetSdkVersion =
+                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
 
-        // For debug builds, log event loop stalls to dropbox for analysis.
-        // Similar logic also appears in ActivityThread.java for system apps.
-        if (!doFlashes && (Build.IS_USER || suppress)) {
-            setCloseGuardEnabled(false);
-            return false;
+        // Starting in HC, we don't allow network usage on the main thread
+        if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
+            builder.detectNetwork();
+            builder.penaltyDeathOnNetwork();
         }
 
-        // Eng builds have flashes on all the time.  The suppression property
-        // overrides this, so we force the behavior only after the short-circuit
-        // check above.
-        if (Build.IS_ENG) {
-            doFlashes = true;
-        }
-
-        // Thread policy controls BlockGuard.
-        int threadPolicyMask =
-                StrictMode.DETECT_DISK_WRITE
-                        | StrictMode.DETECT_DISK_READ
-                        | StrictMode.DETECT_NETWORK;
-
-        if (!Build.IS_USER) {
-            threadPolicyMask |= StrictMode.PENALTY_DROPBOX;
-        }
-        if (doFlashes) {
-            threadPolicyMask |= StrictMode.PENALTY_FLASH;
-        }
-
-        StrictMode.setThreadPolicyMask(threadPolicyMask);
-
-        // VM Policy controls CloseGuard, detection of Activity leaks,
-        // and instance counting.
-        if (Build.IS_USER) {
-            setCloseGuardEnabled(false);
-        } else {
-            VmPolicy.Builder policyBuilder = new VmPolicy.Builder().detectAll();
-            if (!Build.IS_ENG) {
-                // Activity leak detection causes too much slowdown for userdebug because of the
-                // GCs.
-                policyBuilder = policyBuilder.disable(DETECT_VM_ACTIVITY_LEAKS);
+        if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+            // Detect nothing extra
+        } else if (Build.IS_USERDEBUG) {
+            // Detect everything in bundled apps
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.penaltyDropBox();
+                if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
+                    builder.penaltyFlashScreen();
+                }
             }
-            policyBuilder = policyBuilder.penaltyDropBox();
-            if (Build.IS_ENG) {
-                policyBuilder.penaltyLog();
+        } else if (Build.IS_ENG) {
+            // Detect everything in bundled apps
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.penaltyDropBox();
+                builder.penaltyLog();
+                builder.penaltyFlashScreen();
             }
-            // All core system components need to tag their sockets to aid
-            // system health investigations
-            if (android.os.Process.myUid() < android.os.Process.FIRST_APPLICATION_UID) {
-                policyBuilder.enable(DETECT_VM_UNTAGGED_SOCKET);
-            } else {
-                policyBuilder.disable(DETECT_VM_UNTAGGED_SOCKET);
-            }
-            setVmPolicy(policyBuilder.build());
-            setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
         }
-        return true;
+
+        setThreadPolicy(builder.build());
     }
 
     /**
-     * Used by the framework to make network usage on the main thread a fatal error.
+     * Initialize default {@link VmPolicy} for the current VM.
      *
      * @hide
      */
-    public static void enableDeathOnNetwork() {
-        int oldPolicy = getThreadPolicyMask();
-        int newPolicy = oldPolicy | DETECT_NETWORK | PENALTY_DEATH_ON_NETWORK;
-        setThreadPolicyMask(newPolicy);
+    public static void initVmDefaults(ApplicationInfo ai) {
+        final VmPolicy.Builder builder = new VmPolicy.Builder();
+        final int targetSdkVersion =
+                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        // Starting in N, we don't allow file:// Uri exposure
+        if (targetSdkVersion >= Build.VERSION_CODES.N) {
+            builder.detectFileUriExposure();
+            builder.penaltyDeathOnFileUriExposure();
+        }
+
+        if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+            // Detect nothing extra
+        } else if (Build.IS_USERDEBUG) {
+            // Detect everything in bundled apps (except activity leaks, which
+            // are expensive to track)
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.permitActivityLeaks();
+                builder.penaltyDropBox();
+            }
+        } else if (Build.IS_ENG) {
+            // Detect everything in bundled apps
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.penaltyDropBox();
+                builder.penaltyLog();
+            }
+        }
+
+        setVmPolicy(builder.build());
     }
 
     /**
@@ -1205,7 +1299,9 @@
                         sVmPolicy.mask
                                 | DETECT_VM_FILE_URI_EXPOSURE
                                 | PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
-                        sVmPolicy.classInstanceLimit);
+                        sVmPolicy.classInstanceLimit,
+                        sVmPolicy.mListener,
+                        sVmPolicy.mCallbackExecutor);
     }
 
     /**
@@ -1220,7 +1316,9 @@
                         sVmPolicy.mask
                                 & ~(DETECT_VM_FILE_URI_EXPOSURE
                                         | PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
-                        sVmPolicy.classInstanceLimit);
+                        sVmPolicy.classInstanceLimit,
+                        sVmPolicy.mListener,
+                        sVmPolicy.mCallbackExecutor);
     }
 
     /**
@@ -1308,9 +1406,7 @@
             if (tooManyViolationsThisLoop()) {
                 return;
             }
-            BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
-            e.fillInStackTrace();
-            startHandlingViolationException(e);
+            startHandlingViolationException(new DiskWriteViolation());
         }
 
         // Not part of BlockGuard.Policy; just part of StrictMode:
@@ -1321,10 +1417,7 @@
             if (tooManyViolationsThisLoop()) {
                 return;
             }
-            BlockGuard.BlockGuardPolicyException e =
-                    new StrictModeCustomViolation(mPolicyMask, name);
-            e.fillInStackTrace();
-            startHandlingViolationException(e);
+            startHandlingViolationException(new CustomViolation(name));
         }
 
         // Not part of BlockGuard.Policy; just part of StrictMode:
@@ -1335,13 +1428,10 @@
             if (tooManyViolationsThisLoop()) {
                 return;
             }
-            BlockGuard.BlockGuardPolicyException e =
-                    new StrictModeResourceMismatchViolation(mPolicyMask, tag);
-            e.fillInStackTrace();
-            startHandlingViolationException(e);
+            startHandlingViolationException(new ResourceMismatchViolation(tag));
         }
 
-        // Part of BlockGuard.Policy; just part of StrictMode:
+        // Not part of BlockGuard.Policy; just part of StrictMode:
         public void onUnbufferedIO() {
             if ((mPolicyMask & DETECT_UNBUFFERED_IO) == 0) {
                 return;
@@ -1349,10 +1439,7 @@
             if (tooManyViolationsThisLoop()) {
                 return;
             }
-            BlockGuard.BlockGuardPolicyException e =
-                    new StrictModeUnbufferedIOViolation(mPolicyMask);
-            e.fillInStackTrace();
-            startHandlingViolationException(e);
+            startHandlingViolationException(new UnbufferedIoViolation());
         }
 
         // Part of BlockGuard.Policy interface:
@@ -1363,9 +1450,7 @@
             if (tooManyViolationsThisLoop()) {
                 return;
             }
-            BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
-            e.fillInStackTrace();
-            startHandlingViolationException(e);
+            startHandlingViolationException(new DiskReadViolation());
         }
 
         // Part of BlockGuard.Policy interface:
@@ -1379,9 +1464,7 @@
             if (tooManyViolationsThisLoop()) {
                 return;
             }
-            BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
-            e.fillInStackTrace();
-            startHandlingViolationException(e);
+            startHandlingViolationException(new NetworkViolation());
         }
 
         public void setPolicyMask(int policyMask) {
@@ -1393,8 +1476,8 @@
         // has yet occurred).  This sees if we're in an event loop
         // thread and, if so, uses it to roughly measure how long the
         // violation took.
-        void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) {
-            final ViolationInfo info = new ViolationInfo(e, e.getPolicy());
+        void startHandlingViolationException(Violation e) {
+            final ViolationInfo info = new ViolationInfo(e, mPolicyMask);
             info.violationUptimeMillis = SystemClock.uptimeMillis();
             handleViolationWithTimingAttempt(info);
         }
@@ -1423,9 +1506,9 @@
             //
             // TODO: if in gather mode, ignore Looper.myLooper() and always
             //       go into this immediate mode?
-            if (looper == null || (info.policy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
+            if (looper == null || (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
                 info.durationMillis = -1; // unknown (redundant, already set)
-                handleViolation(info);
+                onThreadPolicyViolation(info);
                 return;
             }
 
@@ -1443,7 +1526,7 @@
             }
 
             final IWindowManager windowManager =
-                    (info.policy & PENALTY_FLASH) != 0 ? sWindowManager.get() : null;
+                    info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null;
             if (windowManager != null) {
                 try {
                     windowManager.showStrictModeViolation(true);
@@ -1463,30 +1546,28 @@
             THREAD_HANDLER
                     .get()
                     .postAtFrontOfQueue(
-                            new Runnable() {
-                                public void run() {
-                                    long loopFinishTime = SystemClock.uptimeMillis();
+                            () -> {
+                                long loopFinishTime = SystemClock.uptimeMillis();
 
-                                    // Note: we do this early, before handling the
-                                    // violation below, as handling the violation
-                                    // may include PENALTY_DEATH and we don't want
-                                    // to keep the red border on.
-                                    if (windowManager != null) {
-                                        try {
-                                            windowManager.showStrictModeViolation(false);
-                                        } catch (RemoteException unused) {
-                                        }
+                                // Note: we do this early, before handling the
+                                // violation below, as handling the violation
+                                // may include PENALTY_DEATH and we don't want
+                                // to keep the red border on.
+                                if (windowManager != null) {
+                                    try {
+                                        windowManager.showStrictModeViolation(false);
+                                    } catch (RemoteException unused) {
                                     }
-
-                                    for (int n = 0; n < records.size(); ++n) {
-                                        ViolationInfo v = records.get(n);
-                                        v.violationNumThisLoop = n + 1;
-                                        v.durationMillis =
-                                                (int) (loopFinishTime - v.violationUptimeMillis);
-                                        handleViolation(v);
-                                    }
-                                    records.clear();
                                 }
+
+                                for (int n = 0; n < records.size(); ++n) {
+                                    ViolationInfo v = records.get(n);
+                                    v.violationNumThisLoop = n + 1;
+                                    v.durationMillis =
+                                            (int) (loopFinishTime - v.violationUptimeMillis);
+                                    onThreadPolicyViolation(v);
+                                }
+                                records.clear();
                             });
         }
 
@@ -1495,18 +1576,13 @@
         // violation fired and now (after the violating code ran) due
         // to people who push/pop temporary policy in regions of code,
         // hence the policy being passed around.
-        void handleViolation(final ViolationInfo info) {
-            if (info == null || !info.hasStackTrace()) {
-                Log.wtf(TAG, "unexpected null stacktrace");
-                return;
-            }
+        void onThreadPolicyViolation(final ViolationInfo info) {
+            if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; policy=" + info.mPolicy);
 
-            if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.policy);
-
-            if ((info.policy & PENALTY_GATHER) != 0) {
+            if (info.penaltyEnabled(PENALTY_GATHER)) {
                 ArrayList<ViolationInfo> violations = gatheredViolations.get();
                 if (violations == null) {
-                    violations = new ArrayList<ViolationInfo>(1);
+                    violations = new ArrayList<>(1);
                     gatheredViolations.set(violations);
                 }
                 for (ViolationInfo previous : violations) {
@@ -1535,31 +1611,32 @@
             long timeSinceLastViolationMillis =
                     lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
 
-            if ((info.policy & PENALTY_LOG) != 0
+            if (info.penaltyEnabled(PENALTY_LOG)
                     && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
                 sLogger.log(info);
             }
 
+            final Violation violation = info.mViolation;
+
             // The violationMaskSubset, passed to ActivityManager, is a
             // subset of the original StrictMode policy bitmask, with
             // only the bit violated and penalty bits to be executed
             // by the ActivityManagerService remaining set.
             int violationMaskSubset = 0;
 
-            if ((info.policy & PENALTY_DIALOG) != 0
+            if (info.penaltyEnabled(PENALTY_DIALOG)
                     && timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
                 violationMaskSubset |= PENALTY_DIALOG;
             }
 
-            if ((info.policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
+            if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) {
                 violationMaskSubset |= PENALTY_DROPBOX;
             }
 
             if (violationMaskSubset != 0) {
                 violationMaskSubset |= info.getViolationBit();
-                final int savedPolicyMask = getThreadPolicyMask();
 
-                final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
+                final boolean justDropBox = (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
                 if (justDropBox) {
                     // If all we're going to ask the activity manager
                     // to do is dropbox it (the common case during
@@ -1568,42 +1645,38 @@
                     // isn't always super fast, despite the implementation
                     // in the ActivityManager trying to be mostly async.
                     dropboxViolationAsync(violationMaskSubset, info);
-                    return;
-                }
-
-                // Normal synchronous call to the ActivityManager.
-                try {
-                    // First, remove any policy before we call into the Activity Manager,
-                    // otherwise we'll infinite recurse as we try to log policy violations
-                    // to disk, thus violating policy, thus requiring logging, etc...
-                    // We restore the current policy below, in the finally block.
-                    setThreadPolicyMask(0);
-
-                    ActivityManager.getService()
-                            .handleApplicationStrictModeViolation(
-                                    RuntimeInit.getApplicationObject(), violationMaskSubset, info);
-                } catch (RemoteException e) {
-                    if (e instanceof DeadObjectException) {
-                        // System process is dead; ignore
-                    } else {
-                        Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
-                    }
-                } finally {
-                    // Restore the policy.
-                    setThreadPolicyMask(savedPolicyMask);
+                } else {
+                    handleApplicationStrictModeViolation(violationMaskSubset, info);
                 }
             }
 
-            if ((info.policy & PENALTY_DEATH) != 0) {
-                executeDeathPenalty(info);
+            if ((info.getPolicyMask() & PENALTY_DEATH) != 0) {
+                throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
+            }
+
+            // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
+            // executor finishes before crashing.
+            final OnThreadViolationListener listener = sThreadViolationListener.get();
+            final Executor executor = sThreadViolationExecutor.get();
+            if (listener != null && executor != null) {
+                try {
+                    executor.execute(
+                            () -> {
+                                // Lift violated policy to prevent infinite recursion.
+                                ThreadPolicy oldPolicy = allowThreadViolations();
+                                try {
+                                    listener.onThreadViolation(violation);
+                                } finally {
+                                    setThreadPolicy(oldPolicy);
+                                }
+                            });
+                } catch (RejectedExecutionException e) {
+                    Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
+                }
             }
         }
     }
 
-    private static void executeDeathPenalty(ViolationInfo info) {
-        throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
-    }
-
     /**
      * In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
      * violations but not showing a dialog, not loggging, and not killing the process. In these
@@ -1622,33 +1695,44 @@
 
         if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
 
-        new Thread("callActivityManagerForStrictModeDropbox") {
-            public void run() {
-                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                try {
-                    IActivityManager am = ActivityManager.getService();
-                    if (am == null) {
-                        Log.d(TAG, "No activity manager; failed to Dropbox violation.");
-                    } else {
-                        am.handleApplicationStrictModeViolation(
-                                RuntimeInit.getApplicationObject(), violationMaskSubset, info);
-                    }
-                } catch (RemoteException e) {
-                    if (e instanceof DeadObjectException) {
-                        // System process is dead; ignore
-                    } else {
-                        Log.e(TAG, "RemoteException handling StrictMode violation", e);
-                    }
-                }
-                int outstanding = sDropboxCallsInFlight.decrementAndGet();
-                if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstanding);
+        BackgroundThread.getHandler().post(() -> {
+            handleApplicationStrictModeViolation(violationMaskSubset, info);
+            int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
+            if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
+        });
+    }
+
+    private static void handleApplicationStrictModeViolation(int violationMaskSubset,
+            ViolationInfo info) {
+        final int oldMask = getThreadPolicyMask();
+        try {
+            // First, remove any policy before we call into the Activity Manager,
+            // otherwise we'll infinite recurse as we try to log policy violations
+            // to disk, thus violating policy, thus requiring logging, etc...
+            // We restore the current policy below, in the finally block.
+            setThreadPolicyMask(0);
+
+            IActivityManager am = ActivityManager.getService();
+            if (am == null) {
+                Log.w(TAG, "No activity manager; failed to Dropbox violation.");
+            } else {
+                am.handleApplicationStrictModeViolation(
+                        RuntimeInit.getApplicationObject(), violationMaskSubset, info);
             }
-        }.start();
+        } catch (RemoteException e) {
+            if (e instanceof DeadObjectException) {
+                // System process is dead; ignore
+            } else {
+                Log.e(TAG, "RemoteException handling StrictMode violation", e);
+            }
+        } finally {
+            setThreadPolicyMask(oldMask);
+        }
     }
 
     private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
         public void report(String message, Throwable allocationSite) {
-            onVmPolicyViolation(allocationSite);
+            onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
         }
     }
 
@@ -1686,8 +1770,7 @@
             int limit = policy.classInstanceLimit.get(klass);
             long instances = instanceCounts[i];
             if (instances > limit) {
-                Throwable tr = new InstanceCountViolation(klass, instances, limit);
-                onVmPolicyViolation(tr);
+                onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
             }
         }
     }
@@ -1769,9 +1852,8 @@
      * #setThreadPolicy}.
      */
     public static void enableDefaults() {
-        StrictMode.setThreadPolicy(
-                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+        setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
     }
 
     /** @hide */
@@ -1811,24 +1893,22 @@
 
     /** @hide */
     public static void onSqliteObjectLeaked(String message, Throwable originStack) {
-        Throwable t = new Throwable(message);
-        t.setStackTrace(originStack.getStackTrace());
-        onVmPolicyViolation(t);
+        onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack));
     }
 
     /** @hide */
     public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
-        onVmPolicyViolation(originStack);
+        onVmPolicyViolation(new WebViewMethodCalledOnWrongThreadViolation(originStack));
     }
 
     /** @hide */
     public static void onIntentReceiverLeaked(Throwable originStack) {
-        onVmPolicyViolation(originStack);
+        onVmPolicyViolation(new IntentReceiverLeakedViolation(originStack));
     }
 
     /** @hide */
     public static void onServiceConnectionLeaked(Throwable originStack) {
-        onVmPolicyViolation(originStack);
+        onVmPolicyViolation(new ServiceConnectionLeakedViolation(originStack));
     }
 
     /** @hide */
@@ -1837,19 +1917,13 @@
         if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
             throw new FileUriExposedException(message);
         } else {
-            onVmPolicyViolation(new Throwable(message));
+            onVmPolicyViolation(new FileUriExposedViolation(message));
         }
     }
 
     /** @hide */
     public static void onContentUriWithoutPermission(Uri uri, String location) {
-        final String message =
-                uri
-                        + " exposed beyond app through "
-                        + location
-                        + " without permission grant flags; did you forget"
-                        + " FLAG_GRANT_READ_URI_PERMISSION?";
-        onVmPolicyViolation(new Throwable(message));
+        onVmPolicyViolation(new ContentUriWithoutPermissionViolation(uri, location));
     }
 
     /** @hide */
@@ -1881,33 +1955,28 @@
         }
         msg += HexDump.dumpHexString(firstPacket).trim() + " ";
         final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
-        onVmPolicyViolation(new Throwable(msg), forceDeath);
+        onVmPolicyViolation(new CleartextNetworkViolation(msg), forceDeath);
     }
 
     /** @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(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
+        onVmPolicyViolation(new UntaggedSocketViolation());
     }
 
     // Map from VM violation fingerprint to uptime millis.
-    private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
+    private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();
 
     /** @hide */
-    public static void onVmPolicyViolation(Throwable originStack) {
+    public static void onVmPolicyViolation(Violation originStack) {
         onVmPolicyViolation(originStack, false);
     }
 
     /** @hide */
-    public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) {
+    public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
         final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
         final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
         final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
-        final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
+        final ViolationInfo info = new ViolationInfo(violation, sVmPolicy.mask);
 
         // Erase stuff not relevant for process-wide violations
         info.numAnimationsRunning = 0;
@@ -1916,60 +1985,36 @@
 
         final Integer fingerprint = info.hashCode();
         final long now = SystemClock.uptimeMillis();
-        long lastViolationTime = 0;
+        long lastViolationTime;
         long timeSinceLastViolationMillis = Long.MAX_VALUE;
         synchronized (sLastVmViolationTime) {
             if (sLastVmViolationTime.containsKey(fingerprint)) {
                 lastViolationTime = sLastVmViolationTime.get(fingerprint);
                 timeSinceLastViolationMillis = now - lastViolationTime;
             }
-            if (timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+            if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
                 sLastVmViolationTime.put(fingerprint, now);
             }
         }
-
-        if (penaltyLog && sLogger != null) {
-            sLogger.log(info);
+        if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
+            // Rate limit all penalties.
+            return;
         }
-        if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+
+        if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
             sLogger.log(info);
         }
 
         int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);
 
-        if (penaltyDropbox && !penaltyDeath) {
-            // Common case for userdebug/eng builds.  If no death and
-            // just dropboxing, we can do the ActivityManager call
-            // asynchronously.
-            dropboxViolationAsync(violationMaskSubset, info);
-            return;
-        }
-
-        if (penaltyDropbox && lastViolationTime == 0) {
-            // The violationMask, passed to ActivityManager, is a
-            // subset of the original StrictMode policy bitmask, with
-            // only the bit violated and penalty bits to be executed
-            // by the ActivityManagerService remaining set.
-            final int savedPolicyMask = getThreadPolicyMask();
-            try {
-                // First, remove any policy before we call into the Activity Manager,
-                // otherwise we'll infinite recurse as we try to log policy violations
-                // to disk, thus violating policy, thus requiring logging, etc...
-                // We restore the current policy below, in the finally block.
-                setThreadPolicyMask(0);
-
-                ActivityManager.getService()
-                        .handleApplicationStrictModeViolation(
-                                RuntimeInit.getApplicationObject(), violationMaskSubset, info);
-            } catch (RemoteException e) {
-                if (e instanceof DeadObjectException) {
-                    // System process is dead; ignore
-                } else {
-                    Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
-                }
-            } finally {
-                // Restore the policy.
-                setThreadPolicyMask(savedPolicyMask);
+        if (penaltyDropbox) {
+            if (penaltyDeath) {
+                handleApplicationStrictModeViolation(violationMaskSubset, info);
+            } else {
+                // Common case for userdebug/eng builds.  If no death and
+                // just dropboxing, we can do the ActivityManager call
+                // asynchronously.
+                dropboxViolationAsync(violationMaskSubset, info);
             }
         }
 
@@ -1978,6 +2023,26 @@
             Process.killProcess(Process.myPid());
             System.exit(10);
         }
+
+        // If penaltyDeath, we can't guarantee this callback finishes before the process dies for
+        // all executors. penaltyDeath supersedes penaltyCallback.
+        if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) {
+            final OnVmViolationListener listener = sVmPolicy.mListener;
+            try {
+                sVmPolicy.mCallbackExecutor.execute(
+                        () -> {
+                            // Lift violated policy to prevent infinite recursion.
+                            VmPolicy oldPolicy = allowVmViolations();
+                            try {
+                                listener.onVmViolation(violation);
+                            } finally {
+                                setVmPolicy(oldPolicy);
+                            }
+                        });
+            } catch (RejectedExecutionException e) {
+                Log.e(TAG, "VmPolicy penaltyCallback failed", e);
+            }
+        }
     }
 
     /** Called from Parcel.writeNoException() */
@@ -1998,14 +2063,12 @@
         gatheredViolations.set(null);
     }
 
-    private static class LogStackTrace extends Exception {}
-
     /**
      * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here
      * read back all the encoded violations.
      */
     /* package */ static void readAndHandleBinderCallViolations(Parcel p) {
-        LogStackTrace localCallSite = new LogStackTrace();
+        Throwable localCallSite = new Throwable();
         final int policyMask = getThreadPolicyMask();
         final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
 
@@ -2323,8 +2386,7 @@
 
         long instances = VMDebug.countInstancesOfClass(klass, false);
         if (instances > limit) {
-            Throwable tr = new InstanceCountViolation(klass, instances, limit);
-            onVmPolicyViolation(tr);
+            onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
         }
     }
 
@@ -2337,17 +2399,16 @@
     @TestApi
     public static final class ViolationInfo implements Parcelable {
         /** Stack and violation details. */
-        @Nullable private final Throwable mThrowable;
+        private final Violation mViolation;
 
-        private final Deque<Throwable> mBinderStack = new ArrayDeque<>();
+        /** Path leading to a violation that occurred across binder. */
+        private final Deque<StackTraceElement[]> mBinderStack = new ArrayDeque<>();
 
         /** Memoized stack trace of full violation. */
         @Nullable private String mStackTrace;
-        /** Memoized violation bit. */
-        private int mViolationBit;
 
         /** The strict mode policy mask at the time of violation. */
-        public final int policy;
+        private final int mPolicy;
 
         /** The wall time duration of the violation, when known. -1 when not known. */
         public int durationMillis = -1;
@@ -2377,17 +2438,11 @@
         /** If this is a instance count violation, the number of instances in memory, else -1. */
         public long numInstances = -1;
 
-        /** Create an uninitialized instance of ViolationInfo */
-        public ViolationInfo() {
-            mThrowable = null;
-            policy = 0;
-        }
-
         /** Create an instance of ViolationInfo initialized from an exception. */
-        public ViolationInfo(Throwable tr, int policy) {
-            this.mThrowable = tr;
+        ViolationInfo(Violation tr, int policy) {
+            this.mViolation = tr;
+            this.mPolicy = policy;
             violationUptimeMillis = SystemClock.uptimeMillis();
-            this.policy = policy;
             this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
             Intent broadcastIntent = ActivityThread.getIntentBeingBroadcast();
             if (broadcastIntent != null) {
@@ -2395,7 +2450,7 @@
             }
             ThreadSpanState state = sThisThreadSpanState.get();
             if (tr instanceof InstanceCountViolation) {
-                this.numInstances = ((InstanceCountViolation) tr).mInstances;
+                this.numInstances = ((InstanceCountViolation) tr).getNumberOfInstances();
             }
             synchronized (state) {
                 int spanActiveCount = state.mActiveSize;
@@ -2417,13 +2472,17 @@
 
         /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
         public String getStackTrace() {
-            if (mThrowable != null && mStackTrace == null) {
+            if (mStackTrace == null) {
                 StringWriter sw = new StringWriter();
                 PrintWriter pw = new FastPrintWriter(sw, false, 256);
-                mThrowable.printStackTrace(pw);
-                for (Throwable t : mBinderStack) {
+                mViolation.printStackTrace(pw);
+                for (StackTraceElement[] traces : mBinderStack) {
                     pw.append("# via Binder call with stack:\n");
-                    t.printStackTrace(pw);
+                    for (StackTraceElement traceElement : traces) {
+                        pw.append("\tat ");
+                        pw.append(traceElement.toString());
+                        pw.append('\n');
+                    }
                 }
                 pw.flush();
                 pw.close();
@@ -2439,29 +2498,31 @@
          */
         @TestApi
         public String getViolationDetails() {
-            if (mThrowable != null) {
-                return mThrowable.getMessage();
-            } else {
-                return "";
-            }
+            return mViolation.getMessage();
         }
 
         /**
-         * If this violation has a useful stack trace.
+         * Policy mask at time of violation.
          *
          * @hide
          */
-        public boolean hasStackTrace() {
-            return mThrowable != null;
+        @TestApi
+        public int getPolicyMask() {
+            return mPolicy;
+        }
+
+        boolean penaltyEnabled(int p) {
+            return (mPolicy & p) != 0;
         }
 
         /**
-         * Add a {@link Throwable} from the current process that caused the underlying violation.
+         * Add a {@link Throwable} from the current process that caused the underlying violation. We
+         * only preserve the stack trace elements.
          *
          * @hide
          */
         void addLocalStack(Throwable t) {
-            mBinderStack.addFirst(t);
+            mBinderStack.addFirst(t.getStackTrace());
         }
 
         /**
@@ -2469,37 +2530,47 @@
          *
          * @hide
          */
-        int getViolationBit() {
-            if (mThrowable == null || mThrowable.getMessage() == null) {
-                return 0;
+        @TestApi
+        public int getViolationBit() {
+            if (mViolation instanceof DiskWriteViolation) {
+                return DETECT_DISK_WRITE;
+            } else if (mViolation instanceof DiskReadViolation) {
+                return DETECT_DISK_READ;
+            } else if (mViolation instanceof NetworkViolation) {
+                return DETECT_NETWORK;
+            } else if (mViolation instanceof CustomViolation) {
+                return DETECT_CUSTOM;
+            } else if (mViolation instanceof ResourceMismatchViolation) {
+                return DETECT_RESOURCE_MISMATCH;
+            } else if (mViolation instanceof UnbufferedIoViolation) {
+                return DETECT_UNBUFFERED_IO;
+            } else if (mViolation instanceof SqliteObjectLeakedViolation) {
+                return DETECT_VM_CURSOR_LEAKS;
+            } else if (mViolation instanceof LeakedClosableViolation) {
+                return DETECT_VM_CLOSABLE_LEAKS;
+            } else if (mViolation instanceof InstanceCountViolation) {
+                return DETECT_VM_INSTANCE_LEAKS;
+            } else if (mViolation instanceof IntentReceiverLeakedViolation) {
+                return DETECT_VM_REGISTRATION_LEAKS;
+            } else if (mViolation instanceof ServiceConnectionLeakedViolation) {
+                return DETECT_VM_REGISTRATION_LEAKS;
+            } else if (mViolation instanceof FileUriExposedViolation) {
+                return DETECT_VM_FILE_URI_EXPOSURE;
+            } else if (mViolation instanceof CleartextNetworkViolation) {
+                return DETECT_VM_CLEARTEXT_NETWORK;
+            } else if (mViolation instanceof ContentUriWithoutPermissionViolation) {
+                return DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION;
+            } else if (mViolation instanceof UntaggedSocketViolation) {
+                return DETECT_VM_UNTAGGED_SOCKET;
             }
-            if (mViolationBit != 0) {
-                return mViolationBit;
-            }
-            String message = mThrowable.getMessage();
-            int violationIndex = message.indexOf("violation=");
-            if (violationIndex == -1) {
-                return 0;
-            }
-            int numberStartIndex = violationIndex + "violation=".length();
-            int numberEndIndex = message.indexOf(' ', numberStartIndex);
-            if (numberEndIndex == -1) {
-                numberEndIndex = message.length();
-            }
-            String violationString = message.substring(numberStartIndex, numberEndIndex);
-            try {
-                mViolationBit = Integer.parseInt(violationString);
-                return mViolationBit;
-            } catch (NumberFormatException e) {
-                return 0;
-            }
+            throw new IllegalStateException("missing violation bit");
         }
 
         @Override
         public int hashCode() {
             int result = 17;
-            if (mThrowable != null) {
-                result = 37 * result + mThrowable.hashCode();
+            if (mViolation != null) {
+                result = 37 * result + mViolation.hashCode();
             }
             if (numAnimationsRunning != 0) {
                 result *= 37;
@@ -2527,16 +2598,26 @@
          *     should be removed.
          */
         public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
-            mThrowable = (Throwable) in.readSerializable();
+            mViolation = (Violation) in.readSerializable();
             int binderStackSize = in.readInt();
             for (int i = 0; i < binderStackSize; i++) {
-                mBinderStack.add((Throwable) in.readSerializable());
+                StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
+                for (int j = 0; j < traceElements.length; j++) {
+                    StackTraceElement element =
+                            new StackTraceElement(
+                                    in.readString(),
+                                    in.readString(),
+                                    in.readString(),
+                                    in.readInt());
+                    traceElements[j] = element;
+                }
+                mBinderStack.add(traceElements);
             }
             int rawPolicy = in.readInt();
             if (unsetGatheringBit) {
-                policy = rawPolicy & ~PENALTY_GATHER;
+                mPolicy = rawPolicy & ~PENALTY_GATHER;
             } else {
-                policy = rawPolicy;
+                mPolicy = rawPolicy;
             }
             durationMillis = in.readInt();
             violationNumThisLoop = in.readInt();
@@ -2550,13 +2631,19 @@
         /** Save a ViolationInfo instance to a parcel. */
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeSerializable(mThrowable);
+            dest.writeSerializable(mViolation);
             dest.writeInt(mBinderStack.size());
-            for (Throwable t : mBinderStack) {
-                dest.writeSerializable(t);
+            for (StackTraceElement[] traceElements : mBinderStack) {
+                dest.writeInt(traceElements.length);
+                for (StackTraceElement element : traceElements) {
+                    dest.writeString(element.getClassName());
+                    dest.writeString(element.getMethodName());
+                    dest.writeString(element.getFileName());
+                    dest.writeInt(element.getLineNumber());
+                }
             }
             int start = dest.dataPosition();
-            dest.writeInt(policy);
+            dest.writeInt(mPolicy);
             dest.writeInt(durationMillis);
             dest.writeInt(violationNumThisLoop);
             dest.writeInt(numAnimationsRunning);
@@ -2569,7 +2656,7 @@
                 Slog.d(
                         TAG,
                         "VIO: policy="
-                                + policy
+                                + mPolicy
                                 + " dur="
                                 + durationMillis
                                 + " numLoop="
@@ -2588,10 +2675,8 @@
 
         /** Dump a ViolationInfo instance to a Printer. */
         public void dump(Printer pw, String prefix) {
-            if (mThrowable != null) {
-                pw.println(prefix + "stackTrace: " + getStackTrace());
-            }
-            pw.println(prefix + "policy: " + policy);
+            pw.println(prefix + "stackTrace: " + getStackTrace());
+            pw.println(prefix + "policy: " + mPolicy);
             if (durationMillis != -1) {
                 pw.println(prefix + "durationMillis: " + durationMillis);
             }
@@ -2635,27 +2720,6 @@
                 };
     }
 
-    // Dummy throwable, for now, since we don't know when or where the
-    // leaked instances came from.  We might in the future, but for
-    // now we suppress the stack trace because it's useless and/or
-    // misleading.
-    private static class InstanceCountViolation extends Throwable {
-        private final long mInstances;
-        private final int mLimit;
-
-        private static final StackTraceElement[] FAKE_STACK = {
-            new StackTraceElement(
-                    "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
-        };
-
-        public InstanceCountViolation(Class klass, long instances, int limit) {
-            super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
-            setStackTrace(FAKE_STACK);
-            mInstances = instances;
-            mLimit = limit;
-        }
-    }
-
     private static final class InstanceTracker {
         private static final HashMap<Class<?>, Integer> sInstanceCounts =
                 new HashMap<Class<?>, Integer>();
diff --git a/android/os/SystemClock.java b/android/os/SystemClock.java
index b3d76d7..c52c22d 100644
--- a/android/os/SystemClock.java
+++ b/android/os/SystemClock.java
@@ -16,12 +16,18 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.app.IAlarmManager;
 import android.content.Context;
 import android.util.Slog;
 
 import dalvik.annotation.optimization.CriticalNative;
 
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
 /**
  * Core timekeeping facilities.
  *
@@ -168,6 +174,31 @@
     native public static long uptimeMillis();
 
     /**
+     * Return {@link Clock} that starts at system boot, not counting time spent
+     * in deep sleep.
+     */
+    public static @NonNull Clock uptimeMillisClock() {
+        return new Clock() {
+            @Override
+            public ZoneId getZone() {
+                return ZoneOffset.UTC;
+            }
+            @Override
+            public Clock withZone(ZoneId zone) {
+                throw new UnsupportedOperationException();
+            }
+            @Override
+            public long millis() {
+                return SystemClock.uptimeMillis();
+            }
+            @Override
+            public Instant instant() {
+                return Instant.ofEpochMilli(millis());
+            }
+        };
+    }
+
+    /**
      * Returns milliseconds since boot, including time spent in sleep.
      *
      * @return elapsed milliseconds since boot.
@@ -176,6 +207,31 @@
     native public static long elapsedRealtime();
 
     /**
+     * Return {@link Clock} that starts at system boot, including time spent in
+     * sleep.
+     */
+    public static @NonNull Clock elapsedRealtimeClock() {
+        return new Clock() {
+            @Override
+            public ZoneId getZone() {
+                return ZoneOffset.UTC;
+            }
+            @Override
+            public Clock withZone(ZoneId zone) {
+                throw new UnsupportedOperationException();
+            }
+            @Override
+            public long millis() {
+                return SystemClock.elapsedRealtime();
+            }
+            @Override
+            public Instant instant() {
+                return Instant.ofEpochMilli(millis());
+            }
+        };
+    }
+
+    /**
      * Returns nanoseconds since boot, including time spent in sleep.
      *
      * @return elapsed nanoseconds since boot.
diff --git a/android/os/TokenWatcher.java b/android/os/TokenWatcher.java
index 9b3a2d6..00333da 100644
--- a/android/os/TokenWatcher.java
+++ b/android/os/TokenWatcher.java
@@ -16,17 +16,23 @@
 
 package android.os;
 
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.WeakHashMap;
-import java.util.Set;
 import android.util.Log;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.WeakHashMap;
+
 /**
- * Helper class that helps you use IBinder objects as reference counted
- * tokens.  IBinders make good tokens because we find out when they are
- * removed
+ * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added
+ * to the collection by calling {@link #acquire}, and removed by calling {@link
+ * #release}. IBinders are also implicitly removed when they become weakly
+ * reachable. Each IBinder may be added at most once.
  *
+ * The {@link #acquired} method is invoked by posting to the specified handler
+ * whenever the size of the watched collection becomes nonzero.  The {@link
+ * #released} method is invoked on the specified handler whenever the size of
+ * the watched collection becomes zero.
  */
 public abstract class TokenWatcher
 {
@@ -59,15 +65,23 @@
      * Record that this token has been acquired.  When acquire is called, and
      * the current count is 0, the acquired method is called on the given
      * handler.
-     * 
-     * @param token An IBinder object.  If this token has already been acquired,
-     *              no action is taken.
+     *
+     * Note that the same {@code token} can only be acquired once. If this
+     * {@code token} has already been acquired, no action is taken. The first
+     * subsequent call to {@link #release} will release this {@code token}
+     * immediately.
+     *
+     * @param token An IBinder object.
      * @param tag   A string used by the {@link #dump} method for debugging,
      *              to see who has references.
      */
     public void acquire(IBinder token, String tag)
     {
         synchronized (mTokens) {
+            if (mTokens.containsKey(token)) {
+                return;
+            }
+
             // explicitly checked to avoid bogus sendNotification calls because
             // of the WeakHashMap and the GC
             int oldSize = mTokens.size();
diff --git a/android/os/UpdateEngine.java b/android/os/UpdateEngine.java
index ee0b623..c6149be 100644
--- a/android/os/UpdateEngine.java
+++ b/android/os/UpdateEngine.java
@@ -67,6 +67,7 @@
         public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10;
         public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11;
         public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12;
+        public static final int UPDATED_BUT_NOT_ACTIVE = 52;
     }
 
     /**
diff --git a/android/os/UserHandle.java b/android/os/UserHandle.java
index e8ebf63..6381b56 100644
--- a/android/os/UserHandle.java
+++ b/android/os/UserHandle.java
@@ -27,6 +27,8 @@
  * Representation of a user on the device.
  */
 public final class UserHandle implements Parcelable {
+    // NOTE: keep logic in sync with system/core/libcutils/multiuser.c
+
     /**
      * @hide Range of uids allocated for a user.
      */
@@ -88,6 +90,19 @@
      */
     public static final boolean MU_ENABLED = true;
 
+    /** @hide */
+    public static final int ERR_GID = -1;
+    /** @hide */
+    public static final int AID_ROOT = android.os.Process.ROOT_UID;
+    /** @hide */
+    public static final int AID_APP_START = android.os.Process.FIRST_APPLICATION_UID;
+    /** @hide */
+    public static final int AID_APP_END = android.os.Process.LAST_APPLICATION_UID;
+    /** @hide */
+    public static final int AID_SHARED_GID_START = android.os.Process.FIRST_SHARED_APPLICATION_GID;
+    /** @hide */
+    public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID;
+
     final int mHandle;
 
     /**
@@ -197,13 +212,20 @@
         return getUid(userId, Process.SHARED_USER_GID);
     }
 
-    /**
-     * Returns the shared app gid for a given uid or appId.
-     * @hide
-     */
-    public static int getSharedAppGid(int id) {
-        return Process.FIRST_SHARED_APPLICATION_GID + (id % PER_USER_RANGE)
-                - Process.FIRST_APPLICATION_UID;
+    /** @hide */
+    public static int getSharedAppGid(int uid) {
+        return getSharedAppGid(getUserId(uid), getAppId(uid));
+    }
+
+    /** @hide */
+    public static int getSharedAppGid(int userId, int appId) {
+        if (appId >= AID_APP_START && appId <= AID_APP_END) {
+            return (appId - AID_APP_START) + AID_SHARED_GID_START;
+        } else if (appId >= AID_ROOT && appId <= AID_APP_START) {
+            return appId;
+        } else {
+            return -1;
+        }
     }
 
     /**
@@ -219,13 +241,18 @@
         return appId;
     }
 
-    /**
-     * Returns the cache GID for a given UID or appId.
-     * @hide
-     */
-    public static int getCacheAppGid(int id) {
-        return Process.FIRST_APPLICATION_CACHE_GID + (id % PER_USER_RANGE)
-                - Process.FIRST_APPLICATION_UID;
+    /** @hide */
+    public static int getCacheAppGid(int uid) {
+        return getCacheAppGid(getUserId(uid), getAppId(uid));
+    }
+
+    /** @hide */
+    public static int getCacheAppGid(int userId, int appId) {
+        if (appId >= AID_APP_START && appId <= AID_APP_END) {
+            return getUid(userId, (appId - AID_APP_START) + AID_CACHE_GID_START);
+        } else {
+            return -1;
+        }
     }
 
     /**
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index c54b72d..22967af 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -140,6 +140,18 @@
     public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
 
     /**
+     * Specifies if a user is disallowed from changing the device
+     * language. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_LOCALE = "no_config_locale";
+
+    /**
      * Specifies if a user is disallowed from installing applications.
      * The default value is <code>false</code>.
      *
@@ -792,6 +804,19 @@
     public static final String DISALLOW_AUTOFILL = "no_autofill";
 
     /**
+     * Specifies if user switching is blocked on the current user.
+     *
+     * <p> This restriction can only be set by the device owner, it will be applied to all users.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+
+    /**
      * Application restriction key that is used to indicate the pending arrival
      * of real restrictions for the app.
      *
@@ -917,7 +942,7 @@
     /**
      * Returns whether switching users is currently allowed.
      * <p>For instance switching users is not allowed if the current user is in a phone call,
-     * or system user hasn't been unlocked yet
+     * system user hasn't been unlocked yet, or {@link #DISALLOW_USER_SWITCH} is set.
      * @hide
      */
     public boolean canSwitchUsers() {
@@ -927,7 +952,9 @@
         boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
         boolean inCall = TelephonyManager.getDefault().getCallState()
                 != TelephonyManager.CALL_STATE_IDLE;
-        return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall;
+        boolean isUserSwitchDisallowed = hasUserRestriction(DISALLOW_USER_SWITCH);
+        return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
+                && !isUserSwitchDisallowed;
     }
 
     /**
@@ -1022,12 +1049,22 @@
     }
 
     /**
-     * Used to check if the user making this call is linked to another user. Linked users may have
+     * @hide
+     * @deprecated Use {@link #isRestrictedProfile()}
+     */
+    @Deprecated
+    public boolean isLinkedUser() {
+        return isRestrictedProfile();
+    }
+
+    /**
+     * Returns whether the caller is running as restricted profile. Restricted profile may have
      * a reduced number of available apps, app restrictions and account restrictions.
      * @return whether the user making this call is a linked user
      * @hide
      */
-    public boolean isLinkedUser() {
+    @SystemApi
+    public boolean isRestrictedProfile() {
         try {
             return mService.isRestricted();
         } catch (RemoteException re) {
@@ -1048,6 +1085,20 @@
     }
 
     /**
+     * Returns whether the calling user has at least one restricted profile associated with it.
+     * @return
+     * @hide
+     */
+    @SystemApi
+    public boolean hasRestrictedProfiles() {
+        try {
+            return mService.hasRestrictedProfiles();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks if a user is a guest user.
      * @return whether user is a guest user.
      * @hide
@@ -1067,6 +1118,7 @@
         return user != null && user.isGuest();
     }
 
+
     /**
      * Checks if the calling app is running in a demo user. When running in a demo user,
      * apps can be more helpful to the user, or explain their features in more detail.
@@ -2298,6 +2350,9 @@
         if (!supportsMultipleUsers()) {
             return false;
         }
+        if (hasUserRestriction(DISALLOW_USER_SWITCH)) {
+            return false;
+        }
         // If Demo Mode is on, don't show user switcher
         if (isDeviceInDemoMode(mContext)) {
             return false;
diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java
index 6594cd0..0b007dd 100644
--- a/android/os/storage/StorageManager.java
+++ b/android/os/storage/StorageManager.java
@@ -42,10 +42,12 @@
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IVold;
+import android.os.IVoldTaskListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelableException;
+import android.os.PersistableBundle;
 import android.os.ProxyFileDescriptorCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -87,7 +89,9 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -884,9 +888,32 @@
     }
 
     /** {@hide} */
+    @Deprecated
     public long benchmark(String volId) {
+        final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+        benchmark(volId, new IVoldTaskListener.Stub() {
+            @Override
+            public void onStatus(int status, PersistableBundle extras) {
+                // Ignored
+            }
+
+            @Override
+            public void onFinished(int status, PersistableBundle extras) {
+                result.complete(extras);
+            }
+        });
         try {
-            return mStorageManager.benchmark(volId);
+            // Convert ms to ns
+            return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
+        } catch (Exception e) {
+            return Long.MAX_VALUE;
+        }
+    }
+
+    /** {@hide} */
+    public void benchmark(String volId, IVoldTaskListener listener) {
+        try {
+            mStorageManager.benchmark(volId, listener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/CleartextNetworkViolation.java
similarity index 70%
rename from android/telephony/ims/feature/IRcsFeature.java
rename to android/os/strictmode/CleartextNetworkViolation.java
index e28e1b3..6a0d381 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/CleartextNetworkViolation.java
@@ -11,16 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class CleartextNetworkViolation extends Violation {
+    /** @hide */
+    public CleartextNetworkViolation(String msg) {
+        super(msg);
+    }
 }
diff --git a/android/os/strictmode/ContentUriWithoutPermissionViolation.java b/android/os/strictmode/ContentUriWithoutPermissionViolation.java
new file mode 100644
index 0000000..e78dc79
--- /dev/null
+++ b/android/os/strictmode/ContentUriWithoutPermissionViolation.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.os.strictmode;
+
+import android.net.Uri;
+
+public final class ContentUriWithoutPermissionViolation extends Violation {
+    /** @hide */
+    public ContentUriWithoutPermissionViolation(Uri uri, String location) {
+        super(
+                uri
+                        + " exposed beyond app through "
+                        + location
+                        + " without permission grant flags; did you forget"
+                        + " FLAG_GRANT_READ_URI_PERMISSION?");
+    }
+}
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/CustomViolation.java
similarity index 70%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/CustomViolation.java
index e28e1b3..d4ad067 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/CustomViolation.java
@@ -11,16 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class CustomViolation extends Violation {
+    /** @hide */
+    public CustomViolation(String name) {
+        super(name);
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/DiskReadViolation.java
similarity index 70%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/DiskReadViolation.java
index e28e1b3..fad32db 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/DiskReadViolation.java
@@ -11,16 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class DiskReadViolation extends Violation {
+    /** @hide */
+    public DiskReadViolation() {
+        super(null);
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/DiskWriteViolation.java
similarity index 70%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/DiskWriteViolation.java
index e28e1b3..cb9ca38 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/DiskWriteViolation.java
@@ -11,16 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class DiskWriteViolation extends Violation {
+    /** @hide */
+    public DiskWriteViolation() {
+        super(null);
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/FileUriExposedViolation.java
similarity index 70%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/FileUriExposedViolation.java
index e28e1b3..e3e6f83 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/FileUriExposedViolation.java
@@ -11,16 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class FileUriExposedViolation extends Violation {
+    /** @hide */
+    public FileUriExposedViolation(String msg) {
+        super(msg);
+    }
 }
diff --git a/android/os/strictmode/InstanceCountViolation.java b/android/os/strictmode/InstanceCountViolation.java
new file mode 100644
index 0000000..9ee2c8e
--- /dev/null
+++ b/android/os/strictmode/InstanceCountViolation.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.os.strictmode;
+
+public class InstanceCountViolation extends Violation {
+    private final long mInstances;
+
+    private static final StackTraceElement[] FAKE_STACK = {
+        new StackTraceElement(
+                "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
+    };
+
+    /** @hide */
+    public InstanceCountViolation(Class klass, long instances, int limit) {
+        super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
+        setStackTrace(FAKE_STACK);
+        mInstances = instances;
+    }
+
+    public long getNumberOfInstances() {
+        return mInstances;
+    }
+}
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/IntentReceiverLeakedViolation.java
similarity index 66%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/IntentReceiverLeakedViolation.java
index e28e1b3..f416c94 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/IntentReceiverLeakedViolation.java
@@ -11,16 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class IntentReceiverLeakedViolation extends Violation {
+    /** @hide */
+    public IntentReceiverLeakedViolation(Throwable originStack) {
+        super(null);
+        setStackTrace(originStack.getStackTrace());
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/LeakedClosableViolation.java
similarity index 66%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/LeakedClosableViolation.java
index e28e1b3..c795a6b 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/LeakedClosableViolation.java
@@ -11,16 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class LeakedClosableViolation extends Violation {
+    /** @hide */
+    public LeakedClosableViolation(String message, Throwable allocationSite) {
+        super(message);
+        initCause(allocationSite);
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/NetworkViolation.java
similarity index 70%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/NetworkViolation.java
index e28e1b3..abcf009 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/NetworkViolation.java
@@ -11,16 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class NetworkViolation extends Violation {
+    /** @hide */
+    public NetworkViolation() {
+        super(null);
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/ResourceMismatchViolation.java
similarity index 70%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/ResourceMismatchViolation.java
index e28e1b3..97c4499 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/ResourceMismatchViolation.java
@@ -11,16 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class ResourceMismatchViolation extends Violation {
+    /** @hide */
+    public ResourceMismatchViolation(Object tag) {
+        super(tag.toString());
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/ServiceConnectionLeakedViolation.java
similarity index 65%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/ServiceConnectionLeakedViolation.java
index e28e1b3..2d6b58f 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/ServiceConnectionLeakedViolation.java
@@ -11,16 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class ServiceConnectionLeakedViolation extends Violation {
+    /** @hide */
+    public ServiceConnectionLeakedViolation(Throwable originStack) {
+        super(null);
+        setStackTrace(originStack.getStackTrace());
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/SqliteObjectLeakedViolation.java
similarity index 66%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/SqliteObjectLeakedViolation.java
index e28e1b3..0200220 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/SqliteObjectLeakedViolation.java
@@ -11,16 +11,15 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
+public final class SqliteObjectLeakedViolation extends Violation {
 
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+    /** @hide */
+    public SqliteObjectLeakedViolation(String message, Throwable originStack) {
+        super(message);
+        initCause(originStack);
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/UnbufferedIoViolation.java
similarity index 60%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/UnbufferedIoViolation.java
index e28e1b3..a5c326d 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/UnbufferedIoViolation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,16 +11,18 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
+import android.os.StrictMode.ThreadPolicy.Builder;
 
 /**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
+ * See #{@link Builder#detectUnbufferedIo()}
  */
-
-public interface IRcsFeature {
+public final class UnbufferedIoViolation extends Violation {
+    /** @hide */
+    public UnbufferedIoViolation() {
+        super(null);
+    }
 }
diff --git a/android/os/strictmode/UntaggedSocketViolation.java b/android/os/strictmode/UntaggedSocketViolation.java
new file mode 100644
index 0000000..836a8b9
--- /dev/null
+++ b/android/os/strictmode/UntaggedSocketViolation.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.os.strictmode;
+
+public final class UntaggedSocketViolation extends Violation {
+    /** @hide */
+    public static final String MESSAGE =
+            "Untagged socket detected; use"
+                    + " TrafficStats.setThreadSocketTag() to track all network usage";
+
+    /** @hide */
+    public UntaggedSocketViolation() {
+        super(MESSAGE);
+    }
+}
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/Violation.java
similarity index 70%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/Violation.java
index e28e1b3..31c7d58 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/Violation.java
@@ -11,16 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package android.telephony.ims.feature;
+package android.os.strictmode;
 
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+/** Root class for all StrictMode violations. */
+public abstract class Violation extends Throwable {
+    Violation(String message) {
+        super(message);
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
similarity index 64%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
index e28e1b3..c328d14 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
@@ -11,16 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
+package android.os.strictmode;
 
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class WebViewMethodCalledOnWrongThreadViolation extends Violation {
+    /** @hide */
+    public WebViewMethodCalledOnWrongThreadViolation(Throwable originStack) {
+        super(null);
+        setStackTrace(originStack.getStackTrace());
+    }
 }
diff --git a/android/preference/PreferenceFragment.java b/android/preference/PreferenceFragment.java
index 73fa01e..4c556ef 100644
--- a/android/preference/PreferenceFragment.java
+++ b/android/preference/PreferenceFragment.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -105,7 +104,10 @@
  *
  * @see Preference
  * @see PreferenceScreen
+ *
+ * @deprecated Use {@link android.support.v7.preference.PreferenceFragmentCompat}
  */
+@Deprecated
 public abstract class PreferenceFragment extends Fragment implements
         PreferenceManager.OnPreferenceTreeClickListener {
 
@@ -146,7 +148,11 @@
      * Interface that PreferenceFragment's containing activity should
      * implement to be able to process preference items that wish to
      * switch to a new fragment.
+     *
+     * @deprecated Use {@link
+     * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
      */
+    @Deprecated
     public interface OnPreferenceStartFragmentCallback {
         /**
          * Called when the user has clicked on a Preference that has
diff --git a/android/provider/MediaStore.java b/android/provider/MediaStore.java
index 13e1e26..32d68cd 100644
--- a/android/provider/MediaStore.java
+++ b/android/provider/MediaStore.java
@@ -81,6 +81,13 @@
     public static final String UNHIDE_CALL = "unhide";
 
     /**
+     * The method name used by the media scanner service to reload all localized ringtone titles due
+     * to a locale change.
+     * @hide
+     */
+    public static final String RETRANSLATE_CALL = "update_titles";
+
+    /**
      * This is for internal use by the media scanner only.
      * Name of the (optional) Uri parameter that determines whether to skip deleting
      * the file pointed to by the _data column, when deleting the database entry.
@@ -1358,6 +1365,18 @@
              * @hide
              */
             public static final String GENRE = "genre";
+
+            /**
+             * The resource URI of a localized title, if any
+             * <P>Type: TEXT</P>
+             * Conforms to this pattern:
+             *   Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
+             *   Authority: Package Name of ringtone title provider
+             *   First Path Segment: Type of resource (must be "string")
+             *   Second Path Segment: Resource ID of title
+             * @hide
+             */
+            public static final String TITLE_RESOURCE_URI = "title_resource_uri";
         }
 
         /**
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 62f4bf5..6decc30 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -66,12 +66,14 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.speech.tts.TextToSpeech;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.AndroidException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.MemoryIntArray;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
@@ -210,8 +212,13 @@
 
     /** @hide */
     public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
-    /** @hide */
-    public static final String EXTRA_SUB_ID = "sub_id";
+
+    /**
+     * An int extra specifying a subscription ID.
+     *
+     * @see android.telephony.SubscriptionInfo#getSubscriptionId
+     */
+    public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
 
     /**
      * Activity Action: Modify Airplane mode settings using a voice command.
@@ -915,6 +922,9 @@
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
      * <p>
+     * The subscription ID of the subscription for which available network operators should be
+     * displayed may be optionally specified with {@link #EXTRA_SUB_ID}.
+     * <p>
      * Input: Nothing.
      * <p>
      * Output: Nothing.
@@ -1886,7 +1896,11 @@
                     arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
                 IContentProvider cp = mProviderHolder.getProvider(cr);
+                String prevValue = getStringForUser(cr, name, userHandle);
                 cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+                String newValue = getStringForUser(cr, name, userHandle);
+                StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newValue, prevValue, tag,
+                        makeDefault ? 1 : 0, userHandle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
                 return false;
@@ -2100,6 +2114,9 @@
      * functions for accessing individual settings entries.
      */
     public static final class System extends NameValueTable {
+        // NOTE: If you add new settings here, be sure to add them to
+        // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoSystemSettingsLocked.
+
         private static final float DEFAULT_FONT_SCALE = 1.0f;
 
         /** @hide */
@@ -4549,6 +4566,9 @@
      * APIs for those values, not modified directly by applications.
      */
     public static final class Secure extends NameValueTable {
+        // NOTE: If you add new settings here, be sure to add them to
+        // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoSecureSettingsLocked.
+
         /**
          * The content:// style URL for this table
          */
@@ -5304,6 +5324,15 @@
         public static final String AUTOFILL_SERVICE = "autofill_service";
 
         /**
+         * Experimental autofill feature.
+         *
+         * <p>TODO(b/67867469): remove once feature is finished
+         * @hide
+         */
+        @TestApi
+        public static final String AUTOFILL_FEATURE_FIELD_DETECTION = "autofill_field_detection";
+
+        /**
          * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
          */
         @Deprecated
@@ -7542,6 +7571,9 @@
      * explicitly modify through the system UI or specialized APIs for those values.
      */
     public static final class Global extends NameValueTable {
+        // NOTE: If you add new settings here, be sure to add them to
+        // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoGlobalSettingsLocked.
+
         /**
          * The content:// style URL for global secure settings items.  Not public.
          */
@@ -8007,28 +8039,40 @@
         public static final String HDMI_SYSTEM_AUDIO_CONTROL_ENABLED =
                 "hdmi_system_audio_control_enabled";
 
-       /**
-        * Whether TV will automatically turn on upon reception of the CEC command
-        * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
-        * @hide
-        */
-       public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
-               "hdmi_control_auto_wakeup_enabled";
+        /**
+         * Whether TV will automatically turn on upon reception of the CEC command
+         * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
+         *
+         * @hide
+         */
+        public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
+                "hdmi_control_auto_wakeup_enabled";
 
-       /**
-        * Whether TV will also turn off other CEC devices when it goes to standby mode.
-        * (0 = false, 1 = true)
-        * @hide
-        */
-       public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
-               "hdmi_control_auto_device_off_enabled";
+        /**
+         * Whether TV will also turn off other CEC devices when it goes to standby mode.
+         * (0 = false, 1 = true)
+         *
+         * @hide
+         */
+        public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
+                "hdmi_control_auto_device_off_enabled";
 
-       /**
-        * The interval in milliseconds at which location requests will be throttled when they are
-        * coming from the background.
-        * @hide
-        */
-       public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
+        /**
+         * If <b>true</b>, enables out-of-the-box execution for priv apps.
+         * Default: false
+         * Values: 0 = false, 1 = true
+         *
+         * @hide
+         */
+        public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
+
+        /**
+         * The interval in milliseconds at which location requests will be throttled when they are
+         * coming from the background.
+         *
+         * @hide
+         */
+        public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
                 "location_background_throttle_interval_ms";
 
         /**
@@ -8488,6 +8532,13 @@
        public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
                "network_metered_multipath_preference";
 
+        /**
+         * Network watchlist last report time.
+         * @hide
+         */
+        public static final String NETWORK_WATCHLIST_LAST_REPORT_TIME =
+                "network_watchlist_last_report_time";
+
        /**
         * The thresholds of the wifi throughput badging (SD, HD etc.) as a comma-delimited list of
         * colon-delimited key-value pairs. The key is the badging enum value defined in
@@ -9280,11 +9331,20 @@
         public static final String DEFAULT_DNS_SERVER = "default_dns_server";
 
         /**
-         * Whether to disable DNS over TLS (boolean)
+         * The requested Private DNS mode (string), and an accompanying specifier (string).
+         *
+         * Currently, the specifier holds the chosen provider name when the mode requests
+         * a specific provider. It may be used to store the provider name even when the
+         * mode changes so that temporarily disabling and re-enabling the specific
+         * provider mode does not necessitate retyping the provider hostname.
          *
          * @hide
          */
-        public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+        public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+        /**
+         * @hide
+         */
+        public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
 
         /** {@hide} */
         public static final String
@@ -9418,6 +9478,16 @@
         public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
 
         /**
+         * Battery Saver device specific settings
+         * This is encoded as a key=value list, separated by commas.
+         * See {@link com.android.server.power.BatterySaverPolicy} for the details.
+         *
+         * @hide
+         */
+        public static final String BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS =
+                "battery_saver_device_specific_constants";
+
+        /**
          * Battery anomaly detection specific settings
          * 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
@@ -10094,12 +10164,17 @@
         public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
 
         /**
-         * Whether the Volte is enabled
+         * Whether the Volte is enabled. If this setting is not set then we use the Carrier Config
+         * value {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
          * <p>
          * Type: int (0 for false, 1 for true)
          * @hide
+         * @deprecated Use {@link android.telephony.SubscriptionManager#ENHANCED_4G_MODE_ENABLED}
+         * instead.
          */
-        public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+        @Deprecated
+        public static final String ENHANCED_4G_MODE_ENABLED =
+                SubscriptionManager.ENHANCED_4G_MODE_ENABLED;
 
         /**
          * Whether VT (Video Telephony over IMS) is enabled
@@ -10107,8 +10182,10 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
+         * @deprecated Use {@link android.telephony.SubscriptionManager#VT_IMS_ENABLED} instead.
          */
-        public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+        @Deprecated
+        public static final String VT_IMS_ENABLED = SubscriptionManager.VT_IMS_ENABLED;
 
         /**
          * Whether WFC is enabled
@@ -10116,8 +10193,10 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
+         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ENABLED} instead.
          */
-        public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+        @Deprecated
+        public static final String WFC_IMS_ENABLED = SubscriptionManager.WFC_IMS_ENABLED;
 
         /**
          * WFC mode on home/non-roaming network.
@@ -10125,8 +10204,10 @@
          * Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
          *
          * @hide
+         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_MODE} instead.
          */
-        public static final String WFC_IMS_MODE = "wfc_ims_mode";
+        @Deprecated
+        public static final String WFC_IMS_MODE = SubscriptionManager.WFC_IMS_MODE;
 
         /**
          * WFC mode on roaming network.
@@ -10134,8 +10215,11 @@
          * Type: int - see {@link #WFC_IMS_MODE} for values
          *
          * @hide
+         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_MODE}
+         * instead.
          */
-        public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+        @Deprecated
+        public static final String WFC_IMS_ROAMING_MODE = SubscriptionManager.WFC_IMS_ROAMING_MODE;
 
         /**
          * Whether WFC roaming is enabled
@@ -10143,8 +10227,12 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
+         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_ENABLED}
+         * instead
          */
-        public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+        @Deprecated
+        public static final String WFC_IMS_ROAMING_ENABLED =
+                SubscriptionManager.WFC_IMS_ROAMING_ENABLED;
 
         /**
          * Whether user can enable/disable LTE as a preferred network. A carrier might control
@@ -10370,7 +10458,9 @@
             DOCK_AUDIO_MEDIA_ENABLED,
             ENCODED_SURROUND_OUTPUT,
             LOW_POWER_MODE_TRIGGER_LEVEL,
-            BLUETOOTH_ON
+            BLUETOOTH_ON,
+            PRIVATE_DNS_MODE,
+            PRIVATE_DNS_SPECIFIER
         };
 
         /** @hide */
@@ -10823,7 +10913,7 @@
 
         /** User preferred subscriptions setting.
           * This holds the details of the user selected subscription from the card and
-          * the activation status. Each settings string have the coma separated values
+          * the activation status. Each settings string have the comma separated values
           * iccId,appType,appId,activationStatus,3gppIndex,3gpp2Index
           * @hide
          */
diff --git a/android/provider/Telephony.java b/android/provider/Telephony.java
index 216d28c..d7b6142 100644
--- a/android/provider/Telephony.java
+++ b/android/provider/Telephony.java
@@ -2828,6 +2828,26 @@
          *  @hide
          */
         public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6;
+
+        /**
+         * The owner of the APN.
+         * <p>Type: INTEGER</p>
+         * @hide
+         */
+        public static final String OWNED_BY = "owned_by";
+
+        /**
+         * Possible value for the OWNED_BY field.
+         * APN is owned by DPC.
+         * @hide
+         */
+        public static final int OWNED_BY_DPC = 0;
+        /**
+         * Possible value for the OWNED_BY field.
+         * APN is owned by other sources.
+         * @hide
+         */
+        public static final int OWNED_BY_OTHERS = 1;
     }
 
     /**
@@ -3273,4 +3293,69 @@
          */
         public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
     }
+
+    /**
+     * Contains carrier identification information.
+     * @hide
+     */
+    public static final class CarrierIdentification implements BaseColumns {
+        /**
+         * Numeric operator ID (as String). {@code MCC + MNC}
+         * <P>Type: TEXT </P>
+         */
+        public static final String MCCMNC = "mccmnc";
+
+        /**
+         * Group id level 1 (as String).
+         * <P>Type: TEXT </P>
+         */
+        public static final String GID1 = "gid1";
+
+        /**
+         * Group id level 2 (as String).
+         * <P>Type: TEXT </P>
+         */
+        public static final String GID2 = "gid2";
+
+        /**
+         * Public Land Mobile Network name.
+         * <P>Type: TEXT </P>
+         */
+        public static final String PLMN = "plmn";
+
+        /**
+         * Prefix xpattern of IMSI (International Mobile Subscriber Identity).
+         * <P>Type: TEXT </P>
+         */
+        public static final String IMSI_PREFIX_XPATTERN = "imsi_prefix_xpattern";
+
+        /**
+         * Service Provider Name.
+         * <P>Type: TEXT </P>
+         */
+        public static final String SPN = "spn";
+
+        /**
+         * Prefer APN name.
+         * <P>Type: TEXT </P>
+         */
+        public static final String APN = "apn";
+
+        /**
+         * User facing carrier name.
+         * <P>Type: TEXT </P>
+         */
+        public static final String NAME = "carrier_name";
+
+        /**
+         * A unique carrier id
+         * <P>Type: INTEGER </P>
+         */
+        public static final String CID = "carrier_id";
+
+        /**
+         * The {@code content://} URI for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://carrier_identification");
+    }
 }
diff --git a/android/security/KeyStore.java b/android/security/KeyStore.java
index 7e959a8..399dddd 100644
--- a/android/security/KeyStore.java
+++ b/android/security/KeyStore.java
@@ -20,6 +20,7 @@
 import android.app.Application;
 import android.app.KeyguardManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -53,7 +54,7 @@
 public class KeyStore {
     private static final String TAG = "KeyStore";
 
-    // ResponseCodes
+    // ResponseCodes - see system/security/keystore/include/keystore/keystore.h
     public static final int NO_ERROR = 1;
     public static final int LOCKED = 2;
     public static final int UNINITIALIZED = 3;
@@ -167,10 +168,14 @@
 
     public byte[] get(String key, int uid) {
         try {
+            key = key != null ? key : "";
             return mBinder.get(key, uid);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return null;
+        } catch (android.os.ServiceSpecificException e) {
+            Log.w(TAG, "KeyStore exception", e);
+            return null;
         }
     }
 
@@ -184,6 +189,9 @@
 
     public int insert(String key, byte[] value, int uid, int flags) {
         try {
+            if (value == null) {
+                value = new byte[0];
+            }
             return mBinder.insert(key, value, uid, flags);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -227,6 +235,9 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return null;
+        } catch (android.os.ServiceSpecificException e) {
+            Log.w(TAG, "KeyStore exception", e);
+            return null;
         }
     }
 
@@ -275,6 +286,7 @@
      */
     public boolean unlock(int userId, String password) {
         try {
+            password = password != null ? password : "";
             mError = mBinder.unlock(userId, password);
             return mError == NO_ERROR;
         } catch (RemoteException e) {
@@ -329,16 +341,25 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return null;
+        } catch (android.os.ServiceSpecificException e) {
+            Log.w(TAG, "KeyStore exception", e);
+            return null;
         }
+
     }
 
     public boolean verify(String key, byte[] data, byte[] signature) {
         try {
+            signature = signature != null ? signature : new byte[0];
             return mBinder.verify(key, data, signature) == NO_ERROR;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return false;
+        } catch (android.os.ServiceSpecificException e) {
+            Log.w(TAG, "KeyStore exception", e);
+            return false;
         }
+
     }
 
     public String grant(String key, int uid) {
@@ -431,6 +452,8 @@
     public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
             int flags, KeyCharacteristics outCharacteristics) {
         try {
+            entropy = entropy != null ? entropy : new byte[0];
+            args = args != null ? args : new KeymasterArguments();
             return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -446,6 +469,8 @@
     public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId,
             int uid, KeyCharacteristics outCharacteristics) {
         try {
+            clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+            appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
             return mBinder.getKeyCharacteristics(alias, clientId, appId, uid, outCharacteristics);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -477,6 +502,8 @@
     public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
             KeymasterBlob appId, int uid) {
         try {
+            clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+            appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
             return mBinder.exportKey(alias, format, clientId, appId, uid);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -491,6 +518,8 @@
     public OperationResult begin(String alias, int purpose, boolean pruneable,
             KeymasterArguments args, byte[] entropy, int uid) {
         try {
+            args = args != null ? args : new KeymasterArguments();
+            entropy = entropy != null ? entropy : new byte[0];
             return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, uid);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -500,11 +529,15 @@
 
     public OperationResult begin(String alias, int purpose, boolean pruneable,
             KeymasterArguments args, byte[] entropy) {
+        entropy = entropy != null ? entropy : new byte[0];
+        args = args != null ? args : new KeymasterArguments();
         return begin(alias, purpose, pruneable, args, entropy, UID_SELF);
     }
 
     public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
         try {
+            arguments = arguments != null ? arguments : new KeymasterArguments();
+            input = input != null ? input : new byte[0];
             return mBinder.update(token, arguments, input);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -515,6 +548,9 @@
     public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature,
             byte[] entropy) {
         try {
+            arguments = arguments != null ? arguments : new KeymasterArguments();
+            entropy = entropy != null ? entropy : new byte[0];
+            signature = signature != null ? signature : new byte[0];
             return mBinder.finish(token, arguments, signature, entropy);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -631,6 +667,12 @@
     public int attestKey(
             String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
         try {
+            if (params == null) {
+                params = new KeymasterArguments();
+            }
+            if (outChain == null) {
+                outChain = new KeymasterCertificateChain();
+            }
             return mBinder.attestKey(alias, params, outChain);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -640,6 +682,12 @@
 
     public int attestDeviceIds(KeymasterArguments params, KeymasterCertificateChain outChain) {
         try {
+            if (params == null) {
+                params = new KeymasterArguments();
+            }
+            if (outChain == null) {
+                outChain = new KeymasterCertificateChain();
+            }
             return mBinder.attestDeviceIds(params, outChain);
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
@@ -762,6 +810,10 @@
     }
 
     private long getFingerprintOnlySid() {
+        final PackageManager packageManager = mContext.getPackageManager();
+        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            return 0;
+        }
         FingerprintManager fingerprintManager = mContext.getSystemService(FingerprintManager.class);
         if (fingerprintManager == null) {
             return 0;
diff --git a/android/service/autofill/FieldsDetection.java b/android/service/autofill/FieldsDetection.java
new file mode 100644
index 0000000..550ecf6
--- /dev/null
+++ b/android/service/autofill/FieldsDetection.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+
+/**
+ * Class by service to improve autofillable fields detection by tracking the meaning of fields
+ * manually edited by the user (when they match values provided by the service).
+ *
+ * TODO(b/67867469):
+ *  - proper javadoc
+ *  - unhide / remove testApi
+ *  - add FieldsDetection management so service can set it just once and reference it in further
+ *    calls to improve performance (and also API to refresh it)
+ *  - rename to FieldsDetectionInfo or FieldClassification? (same for CTS tests)
+ *  - add FieldsDetectionUnitTest once API is well-defined
+ * @hide
+ */
+@TestApi
+public final class FieldsDetection implements Parcelable {
+
+    private final AutofillId mFieldId;
+    private final String mRemoteId;
+    private final String mValue;
+
+    /**
+     * Creates a field detection for just one field / value pair.
+     *
+     * @param fieldId autofill id of the field in the screen.
+     * @param remoteId id used by the service to identify the field later.
+     * @param value field value known to the service.
+     *
+     * TODO(b/67867469):
+     *  - proper javadoc
+     *  - change signature to allow more fields / values / match methods
+     *    - might also need to use a builder, where the constructor is the id for the fieldsdetector
+     *    - might need id for values as well
+     *  - add @NonNull / check it / add unit tests
+     *  - make 'value' input more generic so it can accept distance-based match and other matches
+     *  - throw exception if field value is less than X characters (somewhere between 7-10)
+     *  - make sure to limit total number of fields to around 10 or so
+     *  - use AutofillValue instead of String (so it can compare dates, for example)
+     */
+    public FieldsDetection(AutofillId fieldId, String remoteId, String value) {
+        mFieldId = fieldId;
+        mRemoteId = remoteId;
+        mValue = value;
+    }
+
+    /** @hide */
+    public AutofillId getFieldId() {
+        return mFieldId;
+    }
+
+    /** @hide */
+    public String getRemoteId() {
+        return mRemoteId;
+    }
+
+    /** @hide */
+    public String getValue() {
+        return mValue;
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        // Cannot disclose remoteId or value because they could contain PII
+        return new StringBuilder("FieldsDetection: [field=").append(mFieldId)
+                .append(", remoteId_length=").append(mRemoteId.length())
+                .append(", value_length=").append(mValue.length())
+                .append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mFieldId, flags);
+        parcel.writeString(mRemoteId);
+        parcel.writeString(mValue);
+    }
+
+    public static final Parcelable.Creator<FieldsDetection> CREATOR =
+            new Parcelable.Creator<FieldsDetection>() {
+        @Override
+        public FieldsDetection createFromParcel(Parcel parcel) {
+            // TODO(b/67867469): remove comment below if it does not use a builder at the end
+            // 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.
+            return new FieldsDetection(parcel.readParcelable(null), parcel.readString(),
+                    parcel.readString());
+        }
+
+        @Override
+        public FieldsDetection[] newArray(int size) {
+            return new FieldsDetection[size];
+        }
+    };
+}
diff --git a/android/service/autofill/FillEventHistory.java b/android/service/autofill/FillEventHistory.java
index b1857b3..736d9ef 100644
--- a/android/service/autofill/FillEventHistory.java
+++ b/android/service/autofill/FillEventHistory.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -164,6 +165,10 @@
                         dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
                     }
                 }
+                dest.writeString(event.mDetectedRemoteId);
+                if (event.mDetectedRemoteId != null) {
+                    dest.writeInt(event.mDetectedFieldScore);
+                }
             }
         }
     }
@@ -226,6 +231,7 @@
          * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
          * contexts.
          */
+        // TODO(b/67867469): update with field detection behavior
         public static final int TYPE_CONTEXT_COMMITTED = 4;
 
         /** @hide */
@@ -253,6 +259,9 @@
         @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
         @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
 
+        @Nullable private final String mDetectedRemoteId;
+        private final int mDetectedFieldScore;
+
         /**
          * Returns the type of the event.
          *
@@ -355,6 +364,39 @@
         }
 
         /**
+         * Gets the results of the last {@link FieldsDetection} request.
+         *
+         * @return map of edit-distance match ({@code 0} means full match,
+         * {@code 1} means 1 character different, etc...) by remote id (as set in the
+         * {@link FieldsDetection} constructor), or {@code null} if none of the user-input values
+         * matched the requested detection.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
+         * service requested {@link FillResponse.Builder#setFieldsDetection(FieldsDetection) fields
+         * detection}.
+         *
+         * TODO(b/67867469):
+         *  - improve javadoc
+         *  - refine score meaning (for example, should 1 be different of -1?)
+         *  - mention when it's set
+         *  - unhide
+         *  - unhide / remove testApi
+         *  - add @NonNull / check it / add unit tests
+         *
+         * @hide
+         */
+        @TestApi
+        @NonNull public Map<String, Integer> getDetectedFields() {
+            if (mDetectedRemoteId == null || mDetectedFieldScore == -1) {
+                return Collections.emptyMap();
+            }
+
+            final ArrayMap<String, Integer> map = new ArrayMap<>(1);
+            map.put(mDetectedRemoteId, mDetectedFieldScore);
+            return map;
+        }
+
+        /**
          * Returns which fields were available on datasets provided by the service but manually
          * entered by the user.
          *
@@ -430,7 +472,6 @@
          * and belonged to datasets.
          * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
          * respective entry on {@code manuallyFilledFieldIds}.
-         *
          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
          * {@code changedDatasetIds} doesn't match.
          * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
@@ -438,13 +479,15 @@
          *
          * @hide
          */
+        // TODO(b/67867469): document detection field parameters once stable
         public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
                 @Nullable List<String> selectedDatasetIds,
                 @Nullable ArraySet<String> ignoredDatasetIds,
                 @Nullable ArrayList<AutofillId> changedFieldIds,
                 @Nullable ArrayList<String> changedDatasetIds,
                 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
-                @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+                @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
+                @Nullable String detectedRemoteId, int detectedFieldScore) {
             mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                     "eventType");
             mDatasetId = datasetId;
@@ -467,6 +510,8 @@
             }
             mManuallyFilledFieldIds = manuallyFilledFieldIds;
             mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
+            mDetectedRemoteId = detectedRemoteId;
+            mDetectedFieldScore = detectedFieldScore;
         }
 
         @Override
@@ -479,6 +524,8 @@
                     + ", changedDatasetsIds=" + mChangedDatasetIds
                     + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
                     + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+                    + ", detectedRemoteId=" + mDetectedRemoteId
+                    + ", detectedFieldScore=" + mDetectedFieldScore
                     + "]";
         }
     }
@@ -514,11 +561,15 @@
                         } else {
                             manuallyFilledDatasetIds = null;
                         }
+                        final String detectedRemoteId = parcel.readString();
+                        final int detectedFieldScore = detectedRemoteId == null ? -1
+                                : parcel.readInt();
 
                         selection.addEvent(new Event(eventType, datasetId, clientState,
                                 selectedDatasetIds, ignoredDatasets,
                                 changedFieldIds, changedDatasetIds,
-                                manuallyFilledFieldIds, manuallyFilledDatasetIds));
+                                manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                                detectedRemoteId, detectedFieldScore));
                     }
                     return selection;
                 }
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index 2f6342a..4e6a884 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -22,6 +22,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.Activity;
 import android.content.IntentSender;
 import android.content.pm.ParceledListSlice;
@@ -75,6 +76,7 @@
     private final @Nullable AutofillId[] mAuthenticationIds;
     private final @Nullable AutofillId[] mIgnoredIds;
     private final long mDisableDuration;
+    private final @Nullable FieldsDetection mFieldsDetection;
     private final int mFlags;
     private int mRequestId;
 
@@ -87,6 +89,7 @@
         mAuthenticationIds = builder.mAuthenticationIds;
         mIgnoredIds = builder.mIgnoredIds;
         mDisableDuration = builder.mDisableDuration;
+        mFieldsDetection = builder.mFieldsDetection;
         mFlags = builder.mFlags;
         mRequestId = INVALID_REQUEST_ID;
     }
@@ -132,6 +135,11 @@
     }
 
     /** @hide */
+    public @Nullable FieldsDetection getFieldsDetection() {
+        return mFieldsDetection;
+    }
+
+    /** @hide */
     public int getFlags() {
         return mFlags;
     }
@@ -167,6 +175,7 @@
         private AutofillId[] mAuthenticationIds;
         private AutofillId[] mIgnoredIds;
         private long mDisableDuration;
+        private FieldsDetection mFieldsDetection;
         private int mFlags;
         private boolean mDestroyed;
 
@@ -315,6 +324,25 @@
         }
 
         /**
+         * TODO(b/67867469):
+         *  - javadoc it
+         *  - javadoc how to check results
+         *  - unhide
+         *  - unhide / remove testApi
+         *  - throw exception (and document) if response has datasets or saveinfo
+         *  - throw exception (and document) if id on fieldsDetection is ignored
+         *
+         * @hide
+         */
+        @TestApi
+        public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) {
+            throwIfDestroyed();
+            throwIfDisableAutofillCalled();
+            mFieldsDetection = Preconditions.checkNotNull(fieldsDetection);
+            return this;
+        }
+
+        /**
          * Sets flags changing the response behavior.
          *
          * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
@@ -365,7 +393,8 @@
             if (duration <= 0) {
                 throw new IllegalArgumentException("duration must be greater than 0");
             }
-            if (mAuthentication != null || mDatasets != null || mSaveInfo != null) {
+            if (mAuthentication != null || mDatasets != null || mSaveInfo != null
+                    || mFieldsDetection != null) {
                 throw new IllegalStateException("disableAutofill() must be the only method called");
             }
 
@@ -388,11 +417,11 @@
          */
         public FillResponse build() {
             throwIfDestroyed();
-
             if (mAuthentication == null && mDatasets == null && mSaveInfo == null
-                    && mDisableDuration == 0) {
-                throw new IllegalStateException("need to provide at least one DataSet or a "
-                        + "SaveInfo or an authentication with a presentation or disable autofill");
+                    && mDisableDuration == 0 && mFieldsDetection == null) {
+                throw new IllegalStateException("need to provide: at least one DataSet, or a "
+                        + "SaveInfo, or an authentication with a presentation, "
+                        + "or a FieldsDetection, or disable autofill");
             }
             mDestroyed = true;
             return new FillResponse(this);
@@ -430,6 +459,7 @@
                 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
                 .append(", disableDuration=").append(mDisableDuration)
                 .append(", flags=").append(mFlags)
+                .append(", fieldDetection=").append(mFieldsDetection)
                 .append("]")
                 .toString();
     }
@@ -453,6 +483,7 @@
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeParcelableArray(mIgnoredIds, flags);
         parcel.writeLong(mDisableDuration);
+        parcel.writeParcelable(mFieldsDetection, flags);
         parcel.writeInt(mFlags);
         parcel.writeInt(mRequestId);
     }
@@ -488,6 +519,10 @@
             if (disableDuration > 0) {
                 builder.disableAutofill(disableDuration);
             }
+            final FieldsDetection fieldsDetection = parcel.readParcelable(null);
+            if (fieldsDetection != null) {
+                builder.setFieldsDetection(fieldsDetection);
+            }
             builder.setFlags(parcel.readInt());
 
             final FillResponse response = builder.build();
diff --git a/android/service/dreams/DreamService.java b/android/service/dreams/DreamService.java
index 6a15ade..2a245d0 100644
--- a/android/service/dreams/DreamService.java
+++ b/android/service/dreams/DreamService.java
@@ -680,8 +680,8 @@
      *
      * @return The screen state to use while dozing, such as {@link Display#STATE_ON},
      * {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
-     * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
-     * behavior.
+     * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+     * for the default behavior.
      *
      * @see #setDozeScreenState
      * @hide For use by system UI components only.
@@ -700,12 +700,18 @@
      * perform transitions between states while dozing to conserve power and
      * achieve various effects.
      * </p><p>
-     * It is recommended that the state be set to {@link Display#STATE_DOZE_SUSPEND}
-     * once the dream has completely finished drawing and before it releases its wakelock
-     * to allow the display hardware to be fully suspended.  While suspended, the
-     * display will preserve its on-screen contents or hand off control to dedicated
-     * doze hardware if the devices supports it.  If the doze suspend state is
-     * used, the dream must make sure to set the mode back
+     * Some devices will have dedicated hardware ("Sidekick") to animate
+     * the display content while the CPU sleeps. If the dream and the hardware support
+     * this, {@link Display#STATE_ON_SUSPEND} or {@link Display#STATE_DOZE_SUSPEND}
+     * will switch control to the Sidekick.
+     * </p><p>
+     * If not using Sidekick, it is recommended that the state be set to
+     * {@link Display#STATE_DOZE_SUSPEND} once the dream has completely
+     * finished drawing and before it releases its wakelock
+     * to allow the display hardware to be fully suspended.  While suspended,
+     * the display will preserve its on-screen contents.
+     * </p><p>
+     * If the doze suspend state is used, the dream must make sure to set the mode back
      * to {@link Display#STATE_DOZE} or {@link Display#STATE_ON} before drawing again
      * since the display updates may be ignored and not seen by the user otherwise.
      * </p><p>
@@ -716,8 +722,8 @@
      *
      * @param state The screen state to use while dozing, such as {@link Display#STATE_ON},
      * {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
-     * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
-     * behavior.
+     * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+     * for the default behavior.
      *
      * @hide For use by system UI components only.
      */
diff --git a/android/service/euicc/EuiccService.java b/android/service/euicc/EuiccService.java
index 0c2e4b7..cd233b8 100644
--- a/android/service/euicc/EuiccService.java
+++ b/android/service/euicc/EuiccService.java
@@ -97,6 +97,10 @@
     public static final String ACTION_RESOLVE_NO_PRIVILEGES =
             "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
 
+    /** Ask the user to input carrier confirmation code. */
+    public static final String ACTION_RESOLVE_CONFIRMATION_CODE =
+            "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
+
     /** Intent extra set for resolution requests containing the package name of the calling app. */
     public static final String EXTRA_RESOLUTION_CALLING_PACKAGE =
             "android.service.euicc.extra.RESOLUTION_CALLING_PACKAGE";
@@ -105,6 +109,8 @@
     public static final int RESULT_OK = 0;
     /** Result code indicating that an active SIM must be deactivated to perform the operation. */
     public static final int RESULT_MUST_DEACTIVATE_SIM = -1;
+    /** Result code indicating that the user must input a carrier confirmation code. */
+    public static final int RESULT_NEED_CONFIRMATION_CODE = -2;
     // New predefined codes should have negative values.
 
     /** Start of implementation-specific error results. */
@@ -119,10 +125,13 @@
         RESOLUTION_ACTIONS = new ArraySet<>();
         RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
         RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
+        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE);
     }
 
     /** Boolean extra for resolution actions indicating whether the user granted consent. */
     public static final String RESOLUTION_EXTRA_CONSENT = "consent";
+    /** String extra for resolution actions indicating the carrier confirmation code. */
+    public static final String RESOLUTION_EXTRA_CONFIRMATION_CODE = "confirmation_code";
 
     private final IEuiccService.Stub mStubWrapper;
 
diff --git a/android/service/notification/ConditionProviderService.java b/android/service/notification/ConditionProviderService.java
index 3e992ec..6fc689a 100644
--- a/android/service/notification/ConditionProviderService.java
+++ b/android/service/notification/ConditionProviderService.java
@@ -18,6 +18,8 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Service;
 import android.content.ComponentName;
@@ -56,6 +58,8 @@
  *           &lt;/meta-data>
  * &lt;/service></pre>
  *
+ *  <p> Condition providers cannot be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
  */
 public abstract class ConditionProviderService extends Service {
     private final String TAG = ConditionProviderService.class.getSimpleName()
@@ -197,7 +201,11 @@
         return mProvider;
     }
 
-    private boolean isBound() {
+    /**
+     * @hide
+     */
+    @TestApi
+    public boolean isBound() {
         if (mProvider == null) {
             Log.w(TAG, "Condition provider service not yet bound.");
             return false;
diff --git a/android/service/notification/NotificationListenerService.java b/android/service/notification/NotificationListenerService.java
index 08d3118..dac663e 100644
--- a/android/service/notification/NotificationListenerService.java
+++ b/android/service/notification/NotificationListenerService.java
@@ -21,6 +21,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
@@ -82,6 +83,8 @@
  * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
  * or after {@link #onListenerDisconnected()}.
  * </p>
+ * <p> Notification listeners cannot get notification access or be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
  */
 public abstract class NotificationListenerService extends Service {
 
diff --git a/android/service/voice/VoiceInteractionSession.java b/android/service/voice/VoiceInteractionSession.java
index 625dd9e..cd177c4 100644
--- a/android/service/voice/VoiceInteractionSession.java
+++ b/android/service/voice/VoiceInteractionSession.java
@@ -16,6 +16,8 @@
 
 package android.service.voice;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.Dialog;
@@ -46,7 +48,6 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
@@ -63,8 +64,6 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 /**
  * An active voice interaction session, providing a facility for the implementation
  * to interact with the user in the voice interaction layer.  The user interface is
@@ -110,16 +109,6 @@
      */
     public static final int SHOW_SOURCE_ACTIVITY = 1<<4;
 
-    // Keys for Bundle values
-    /** @hide */
-    public static final String KEY_DATA = "data";
-    /** @hide */
-    public static final String KEY_STRUCTURE = "structure";
-    /** @hide */
-    public static final String KEY_CONTENT = "content";
-    /** @hide */
-    public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
-
     final Context mContext;
     final HandlerCaller mHandlerCaller;
 
@@ -1423,9 +1412,7 @@
     public void setContentView(View view) {
         ensureWindowCreated();
         mContentFrame.removeAllViews();
-        mContentFrame.addView(view, new FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT));
+        mContentFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         mContentFrame.requestApplyInsets();
     }
 
diff --git a/android/support/LibraryGroups.java b/android/support/LibraryGroups.java
new file mode 100644
index 0000000..feaefbc
--- /dev/null
+++ b/android/support/LibraryGroups.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+/**
+ * The list of maven group names of all the libraries in this project.
+ */
+public class LibraryGroups {
+    public static final String SUPPORT = "com.android.support";
+    public static final String ROOM = "android.arch.persistence.room";
+    public static final String PERSISTENCE = "android.arch.persistence";
+    public static final String LIFECYCLE = "android.arch.lifecycle";
+    public static final String ARCH_CORE = "android.arch.core";
+    public static final String PAGING = "android.arch.paging";
+    public static final String NAVIGATION = "android.arch.navigation";
+}
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index 2f5730a..efa0cba 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-rc1");
+    private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0");
 
     /**
      * Version code for Room
diff --git a/android/support/SourceJarTaskHelper.java b/android/support/SourceJarTaskHelper.java
new file mode 100644
index 0000000..9fbd1db
--- /dev/null
+++ b/android/support/SourceJarTaskHelper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+import com.android.build.gradle.LibraryExtension;
+import com.android.builder.core.BuilderConstants;
+
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.bundling.Jar;
+
+/**
+ * Helper class to handle creation of source jars.
+ */
+public class SourceJarTaskHelper {
+    /**
+     * Sets up a source jar task for an Android library project.
+     */
+    public static void setUpAndroidProject(Project project, LibraryExtension extension) {
+        // Create sources jar for release builds
+        extension.getLibraryVariants().all(libraryVariant -> {
+            if (!libraryVariant.getBuildType().getName().equals(BuilderConstants.RELEASE)) {
+                return; // Skip non-release builds.
+            }
+
+            Jar sourceJar = project.getTasks().create("sourceJarRelease", Jar.class);
+            sourceJar.setPreserveFileTimestamps(false);
+            sourceJar.setClassifier("sources");
+            sourceJar.from(extension.getSourceSets().findByName("main").getJava().getSrcDirs());
+            project.getArtifacts().add("archives", sourceJar);
+        });
+    }
+
+    /**
+     * Sets up a source jar task for a Java library project.
+     */
+    public static void setUpJavaProject(Project project) {
+        Jar sourceJar = project.getTasks().create("sourceJar", Jar.class);
+        sourceJar.setPreserveFileTimestamps(false);
+        sourceJar.setClassifier("sources");
+        JavaPluginConvention convention =
+                project.getConvention().getPlugin(JavaPluginConvention.class);
+        sourceJar.from(convention.getSourceSets().findByName("main").getAllSource().getSrcDirs());
+        project.getArtifacts().add("archives", sourceJar);
+    }
+}
diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java
index 7100218..f46c652 100644
--- a/android/support/car/drawer/CarDrawerActivity.java
+++ b/android/support/car/drawer/CarDrawerActivity.java
@@ -46,7 +46,7 @@
  *
  * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
  * CarDrawerAdapter for the next level to
- * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}.
+ * {@link CarDrawerController#pushAdapter(CarDrawerAdapter)}.
  *
  * <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
  * derivative.
diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java
index 4d9f4e9..7b23714 100644
--- a/android/support/car/drawer/CarDrawerController.java
+++ b/android/support/car/drawer/CarDrawerController.java
@@ -19,16 +19,19 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.support.annotation.AnimRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.car.R;
 import android.support.car.widget.PagedListView;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.Toolbar;
 import android.view.Gravity;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.animation.AnimationUtils;
 import android.widget.ProgressBar;
 
 import java.util.Stack;
@@ -39,13 +42,21 @@
  * navigation.
  */
 public class CarDrawerController {
+    /** An animation for when a user navigates into a submenu. */
+    @AnimRes
+    private static final int DRILL_DOWN_ANIM = R.anim.fade_in_trans_right_layout_anim;
+
+    /** An animation for when a user navigates up (when the back button is pressed). */
+    @AnimRes
+    private static final int NAVIGATE_UP_ANIM = R.anim.fade_in_trans_left_layout_anim;
+
     /** The amount that the drawer has been opened before its color should be switched. */
     private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
 
     /**
      * A representation of the hierarchy of navigation being displayed in the list. The ordering of
      * this stack is the order that the user has visited each level. When the user navigates up,
-     * the adapters are poopped from this list.
+     * the adapters are popped from this list.
      */
     private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
 
@@ -78,16 +89,14 @@
             ActionBarDrawerToggle drawerToggle) {
         mToolbar = toolbar;
         mContext = drawerLayout.getContext();
-
+        mDrawerToggle = drawerToggle;
         mDrawerLayout = drawerLayout;
 
         mDrawerContent = drawerLayout.findViewById(R.id.drawer_content);
         mDrawerList = drawerLayout.findViewById(R.id.drawer_list);
         mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
-
         mProgressBar = drawerLayout.findViewById(R.id.drawer_progress);
 
-        mDrawerToggle = drawerToggle;
         setupDrawerToggling();
     }
 
@@ -104,7 +113,13 @@
             return;
         }
 
-        mAdapterStack.push(rootAdapter);
+        // The root adapter is always the last item in the stack.
+        if (mAdapterStack.size() > 0) {
+            mAdapterStack.set(0, rootAdapter);
+        } else {
+            mAdapterStack.push(rootAdapter);
+        }
+
         setToolbarTitleFrom(rootAdapter);
         mDrawerList.setAdapter(rootAdapter);
     }
@@ -120,10 +135,11 @@
      *
      * @param adapter Adapter for next level of content in the drawer.
      */
-    public final void switchToAdapter(CarDrawerAdapter adapter) {
+    public final void pushAdapter(CarDrawerAdapter adapter) {
         mAdapterStack.peek().setTitleChangeListener(null);
         mAdapterStack.push(adapter);
-        switchToAdapterInternal(adapter);
+        setDisplayAdapter(adapter);
+        runLayoutAnimation(DRILL_DOWN_ANIM);
     }
 
     /** Close the drawer. */
@@ -264,15 +280,15 @@
     }
 
     /**
-     * Sets the navigation drawer's title to be the one supplied by the given adapter and updates
-     * the navigation drawer list with the adapter's contents.
+     * Sets the given adapter as the one displaying the current contents of the drawer.
+     *
+     * <p>The drawer's title will also be derived from the given adapter.
      */
-    private void switchToAdapterInternal(CarDrawerAdapter adapter) {
+    private void setDisplayAdapter(CarDrawerAdapter adapter) {
         setToolbarTitleFrom(adapter);
         // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
         // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
         mDrawerList.getRecyclerView().setAdapter(adapter);
-        scrollToPosition(0);
     }
 
     /**
@@ -290,7 +306,8 @@
         CarDrawerAdapter adapter = mAdapterStack.pop();
         adapter.setTitleChangeListener(null);
         adapter.cleanup();
-        switchToAdapterInternal(mAdapterStack.peek());
+        setDisplayAdapter(mAdapterStack.peek());
+        runLayoutAnimation(NAVIGATE_UP_ANIM);
         return true;
     }
 
@@ -301,6 +318,18 @@
             adapter.setTitleChangeListener(null);
             adapter.cleanup();
         }
-        switchToAdapterInternal(mAdapterStack.peek());
+        setDisplayAdapter(mAdapterStack.peek());
+        runLayoutAnimation(NAVIGATE_UP_ANIM);
+    }
+
+    /**
+     * Runs the given layout animation on the PagedListView. Running this animation will also
+     * refresh the contents of the list.
+     */
+    private void runLayoutAnimation(@AnimRes int animation) {
+        RecyclerView recyclerView = mDrawerList.getRecyclerView();
+        recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, animation));
+        recyclerView.getAdapter().notifyDataSetChanged();
+        recyclerView.scheduleLayoutAnimation();
     }
 }
diff --git a/android/support/car/utils/ColumnCalculator.java b/android/support/car/utils/ColumnCalculator.java
index 96e081b..fa5dd43 100644
--- a/android/support/car/utils/ColumnCalculator.java
+++ b/android/support/car/utils/ColumnCalculator.java
@@ -65,7 +65,7 @@
 
     private ColumnCalculator(Context context) {
         Resources res = context.getResources();
-        int marginSize = res.getDimensionPixelSize(R.dimen.car_screen_margin_size);
+        int marginSize = res.getDimensionPixelSize(R.dimen.car_margin);
         mGutterSize = res.getDimensionPixelSize(R.dimen.car_screen_gutter_size);
         mNumOfColumns = res.getInteger(R.integer.car_screen_num_of_columns);
 
diff --git a/android/support/car/widget/CarItemAnimator.java b/android/support/car/widget/CarItemAnimator.java
index 4dd3212..ef22c48 100644
--- a/android/support/car/widget/CarItemAnimator.java
+++ b/android/support/car/widget/CarItemAnimator.java
@@ -22,9 +22,9 @@
 /** {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior. */
 public class CarItemAnimator extends DefaultItemAnimator {
 
-    private final CarLayoutManager mLayoutManager;
+    private final PagedLayoutManager mLayoutManager;
 
-    public CarItemAnimator(CarLayoutManager layoutManager) {
+    public CarItemAnimator(PagedLayoutManager layoutManager) {
         mLayoutManager = layoutManager;
     }
 
diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java
index 2684c58..bb9cb71 100644
--- a/android/support/car/widget/CarRecyclerView.java
+++ b/android/support/car/widget/CarRecyclerView.java
@@ -26,7 +26,7 @@
 import android.view.ViewGroup;
 
 /**
- * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate.
+ * Custom {@link RecyclerView} that helps {@link PagedLayoutManager} 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)}.
@@ -57,7 +57,7 @@
     @Override
     public boolean fling(int velocityX, int velocityY) {
         mWasFlingCalledForGesture = true;
-        return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
+        return ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
     }
 
     @Override
@@ -69,7 +69,7 @@
         int action = e.getActionMasked();
         if (action == MotionEvent.ACTION_UP) {
             if (!mWasFlingCalledForGesture) {
-                ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
+                ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
             }
             mWasFlingCalledForGesture = false;
         }
@@ -102,7 +102,7 @@
      * number of items that fit completely on the screen.
      */
     public void pageUp() {
-        CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+        PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
         int pageUpPosition = lm.getPageUpPosition();
         if (pageUpPosition == -1) {
             return;
@@ -116,7 +116,7 @@
      * number of items that fit completely on the screen.
      */
     public void pageDown() {
-        CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+        PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
         int pageDownPosition = lm.getPageDownPosition();
         if (pageDownPosition == -1) {
             return;
diff --git a/android/support/car/widget/CarLayoutManager.java b/android/support/car/widget/PagedLayoutManager.java
similarity index 97%
rename from android/support/car/widget/CarLayoutManager.java
rename to android/support/car/widget/PagedLayoutManager.java
index d0d3a9e..c4f469a 100644
--- a/android/support/car/widget/CarLayoutManager.java
+++ b/android/support/car/widget/PagedLayoutManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.graphics.PointF;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
@@ -46,9 +48,9 @@
  *
  * <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)}.
+ *       PagedLayoutManager, 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
@@ -57,8 +59,8 @@
  *
  * This LayoutManger should be used with {@link CarRecyclerView}.
  */
-public class CarLayoutManager extends RecyclerView.LayoutManager {
-    private static final String TAG = "CarLayoutManager";
+public class PagedLayoutManager extends RecyclerView.LayoutManager {
+    private static final String TAG = "PagedLayoutManager";
 
     /**
      * Any fling below the threshold will just scroll to the top fully visible row. The units is
@@ -166,7 +168,7 @@
     /** Set the anchor to the following position on the next layout pass. */
     private int mPendingScrollPosition = -1;
 
-    public CarLayoutManager(Context context) {
+    public PagedLayoutManager(Context context) {
         mContext = context;
     }
 
@@ -919,6 +921,55 @@
         return mLowerPageBreakPosition;
     }
 
+    @Override
+    public Parcelable onSaveInstanceState() {
+        SavedState savedState = new SavedState();
+        savedState.mFirstChildPosition = getFirstFullyVisibleChildPosition();
+        return savedState;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (state instanceof SavedState) {
+            scrollToPosition(((SavedState) state).mFirstChildPosition);
+        }
+    }
+
+    /** The state that will be saved across configuration changes. */
+    static class SavedState implements Parcelable {
+        /** The position of the first visible child view in the list. */
+        int mFirstChildPosition;
+
+        SavedState() {}
+
+        private SavedState(Parcel in) {
+            mFirstChildPosition = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mFirstChildPosition);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+
     /**
      * Layout the anchor row. The anchor row is the first fully visible row.
      *
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 4652700..4695c45 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -23,7 +23,11 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -33,6 +37,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -41,13 +46,19 @@
 /**
  * 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.
+ * left side.
  */
 public class PagedListView extends FrameLayout {
     /** Default maximum number of clicks allowed on a list */
     public static final int DEFAULT_MAX_CLICKS = 6;
 
     /**
+     * Value to pass to {@link #setMaxPages(int)} to indicate there is no restriction on the
+     * maximum number of pages to show.
+     */
+    public static final int UNLIMITED_PAGES = -1;
+
+    /**
      * The amount of time after settling to wait before autoscrolling to the next page when the user
      * holds down a pagination button.
      */
@@ -57,7 +68,7 @@
     private static final int INVALID_RESOURCE_ID = -1;
 
     protected final CarRecyclerView mRecyclerView;
-    protected final CarLayoutManager mLayoutManager;
+    protected final PagedLayoutManager mLayoutManager;
     protected final Handler mHandler = new Handler();
     private final boolean mScrollBarEnabled;
     private final PagedScrollBarView mScrollBarView;
@@ -65,8 +76,8 @@
     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;
+    /** Maximum number of pages to show. */
+    private int mMaxPages;
 
     protected OnScrollListener mOnScrollListener;
 
@@ -115,8 +126,6 @@
      * 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);
@@ -151,7 +160,7 @@
 
         mMaxPages = getDefaultMaxPages();
 
-        mLayoutManager = new CarLayoutManager(context);
+        mLayoutManager = new PagedLayoutManager(context);
         mLayoutManager.setOffsetRows(offsetRows);
         mRecyclerView.setLayoutManager(mLayoutManager);
         mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
@@ -162,7 +171,7 @@
         if (offsetScrollBar) {
             MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
             params.setMarginStart(getResources().getDimensionPixelSize(
-                    R.dimen.car_screen_margin_size));
+                    R.dimen.car_margin));
             params.setMarginEnd(
                     a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0));
             mRecyclerView.setLayoutParams(params);
@@ -180,6 +189,11 @@
                     dividerStartId, dividerEndId));
         }
 
+        int itemSpacing = a.getDimensionPixelSize(R.styleable.PagedListView_itemSpacing, 0);
+        if (itemSpacing > 0) {
+            mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+        }
+
         // 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
@@ -212,6 +226,16 @@
                     }
                 });
 
+        Drawable upButtonIcon = a.getDrawable(R.styleable.PagedListView_upButtonIcon);
+        if (upButtonIcon != null) {
+            setUpButtonIcon(upButtonIcon);
+        }
+
+        Drawable downButtonIcon = a.getDrawable(R.styleable.PagedListView_downButtonIcon);
+        if (downButtonIcon != null) {
+            setDownButtonIcon(downButtonIcon);
+        }
+
         mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
 
         // Modify the layout the Scroll Bar is not visible.
@@ -236,7 +260,7 @@
         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);
+            mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_PAGE);
         }
         return super.onInterceptTouchEvent(e);
     }
@@ -246,7 +270,7 @@
         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);
+        mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
     }
 
     /**
@@ -312,19 +336,25 @@
         mHandler.post(mUpdatePaginationRunnable);
     }
 
+    /** Sets the icon to be used for the up button. */
+    public void setUpButtonIcon(Drawable icon) {
+        mScrollBarView.setUpButtonIcon(icon);
+    }
+
+    /** Sets the icon to be used for the down button. */
+    public void setDownButtonIcon(Drawable icon) {
+        mScrollBarView.setDownButtonIcon(icon);
+    }
+
     /**
      * Sets the adapter for the list.
      *
-     * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw an {@link
-     * IllegalArgumentException}.
+     * <p>The given Adapter can implement {@link ItemCap} if it wishes to control the behavior of
+     * a max number of items. Otherwise, methods in the PagedListView to limit the content, such as
+     * {@link #setMaxPages(int)}, will do nothing.
      */
     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();
@@ -333,7 +363,7 @@
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
     @NonNull
-    public CarLayoutManager getLayoutManager() {
+    public PagedLayoutManager getLayoutManager() {
         return mLayoutManager;
     }
 
@@ -345,15 +375,19 @@
 
     /**
      * 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.
+     * 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.
+     * <p>Passing {@link #UNLIMITED_PAGES} will remove any restrictions on a maximum number
+     * of pages.
+     *
+     * <p>Note that for any restriction on maximum pages to work, the adapter passed to this
+     * PagedListView needs to implement {@link ItemCap}.
+     *
+     * @param maxPages The maximum number of pages that fit on the screen. Should be positive or
+     * {@link #UNLIMITED_PAGES}.
      */
     public void setMaxPages(int maxPages) {
-        if (maxPages < 0) {
-            return;
-        }
-        mMaxPages = maxPages;
+        mMaxPages = Math.max(UNLIMITED_PAGES, maxPages);
         updateMaxItems();
     }
 
@@ -362,7 +396,8 @@
      * {@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.
+     * @return The maximum number of pages to be shown or {@link #UNLIMITED_PAGES} if there is
+     * no limit.
      */
     public int getMaxPages() {
         return mMaxPages;
@@ -370,7 +405,7 @@
 
     /**
      * 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.
+     * PagedLayoutManager is null or the height of the first child is 0, it will return 1.
      */
     public int getRowsPerPage() {
         return mRowsPerPage;
@@ -422,6 +457,32 @@
     }
 
     /**
+     * Sets spacing between each item in the list. The spacing will not be added before the first
+     * item and after the last.
+     *
+     * @param itemSpacing the spacing between each item.
+     */
+    public void setItemSpacing(int itemSpacing) {
+        ItemSpacingDecoration existing = null;
+        for (int i = 0, count = mRecyclerView.getItemDecorationCount(); i < count; i++) {
+            RecyclerView.ItemDecoration itemDecoration = mRecyclerView.getItemDecorationAt(i);
+            if (itemDecoration instanceof ItemSpacingDecoration) {
+                existing = (ItemSpacingDecoration) itemDecoration;
+                break;
+            }
+        }
+
+        if (itemSpacing == 0 && existing != null) {
+            mRecyclerView.removeItemDecoration(existing);
+        } else if (existing == null) {
+            mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+        } else {
+            existing.setItemSpacing(itemSpacing);
+        }
+        mRecyclerView.invalidateItemDecorations();
+    }
+
+    /**
      * Adds an {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} to this
      * PagedListView.
      *
@@ -520,6 +581,7 @@
             return;
         }
         mDefaultMaxPages = newDefault;
+        resetMaxPages();
     }
 
     /** Returns the default number of pages the list should have */
@@ -646,8 +708,15 @@
             return;
         }
 
-        final int originalCount = mAdapter.getItemCount();
+        // Ensure mRowsPerPage regardless of if the adapter implements ItemCap.
         updateRowsPerPage();
+
+        // If the adapter does not implement ItemCap, then the max items on it cannot be updated.
+        if (!(mAdapter instanceof ItemCap)) {
+            return;
+        }
+
+        final int originalCount = mAdapter.getItemCount();
         ((ItemCap) mAdapter).setMaxItems(calculateMaxItemCount());
         final int newCount = mAdapter.getItemCount();
         if (newCount == originalCount) {
@@ -683,6 +752,78 @@
         }
     }
 
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        SavedState savedState = new SavedState(super.onSaveInstanceState());
+        savedState.mLayoutManagerState = mLayoutManager.onSaveInstanceState();
+        return savedState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState savedState = (SavedState) state;
+        mLayoutManager.onRestoreInstanceState(savedState.mLayoutManagerState);
+        super.onRestoreInstanceState(savedState.getSuperState());
+    }
+
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        // There is the possibility of multiple PagedListViews on a page. This means that the ids
+        // of the child Views of PagedListView are no longer unique, and onSaveInstanceState()
+        // cannot be used. As a result, PagedListViews needs to manually dispatch the instance
+        // states. Call dispatchFreezeSelfOnly() so that no child views have onSaveInstanceState()
+        // called by the system.
+        dispatchFreezeSelfOnly(container);
+    }
+
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        // Prevent onRestoreInstanceState() from being called on child Views. Instead, PagedListView
+        // will manually handle passing the state. See the comment in dispatchSaveInstanceState()
+        // for more information.
+        dispatchThawSelfOnly(container);
+    }
+
+    /** The state that will be saved across configuration changes. */
+    private static class SavedState extends BaseSavedState {
+        /** The state of the {@link #mLayoutManager} of this PagedListView. */
+        Parcelable mLayoutManagerState;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            mLayoutManagerState =
+                    in.readParcelable(PagedLayoutManager.SavedState.class.getClassLoader());
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeParcelable(mLayoutManagerState, flags);
+        }
+
+        public static final ClassLoaderCreator<SavedState> CREATOR =
+                new ClassLoaderCreator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel source, ClassLoader loader) {
+                        return new SavedState(source);
+                    }
+
+                    @Override
+                    public SavedState createFromParcel(Parcel source) {
+                        return createFromParcel(source, null /* loader */);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+
     private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
             new RecyclerView.OnScrollListener() {
                 @Override
@@ -766,16 +907,50 @@
     }
 
     /**
+     * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will add spacing
+     * between each item in the RecyclerView that it is added to.
+     */
+    private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
+
+        private int mHalfItemSpacing;
+
+        private ItemSpacingDecoration(int itemSpacing) {
+            mHalfItemSpacing = itemSpacing / 2;
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            // Skip top offset for first item and bottom offset for last.
+            int position = parent.getChildAdapterPosition(view);
+            if (position > 0) {
+                outRect.top = mHalfItemSpacing;
+            }
+            if (position < state.getItemCount() - 1) {
+                outRect.bottom = mHalfItemSpacing;
+            }
+        }
+
+        /**
+         * @param itemSpacing sets spacing between each item.
+         */
+        public void setItemSpacing(int itemSpacing) {
+            mHalfItemSpacing = itemSpacing / 2;
+        }
+    }
+
+    /**
      * 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 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;
+        @IdRes private final int mDividerEndId;
 
         /**
          * @param dividerStartMargin The start offset of the dividing line. This offset will be
@@ -792,7 +967,7 @@
             mContext = context;
             mDividerStartMargin = dividerStartMargin;
             mDividerStartId = dividerStartId;
-            mDvidierEndId = dividerEndId;
+            mDividerEndId = dividerEndId;
 
             Resources res = context.getResources();
             mPaint = new Paint();
@@ -807,16 +982,20 @@
 
         @Override
         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
-            for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++) {
+            // Draw a divider line between each item. No need to draw the line for the last item.
+            for (int i = 0, childCount = parent.getChildCount(); i < childCount - 1; i++) {
                 View container = parent.getChildAt(i);
+                View nextContainer = parent.getChildAt(i + 1);
+                int spacing = nextContainer.getTop() - container.getBottom();
+
                 View startChild =
                         mDividerStartId != INVALID_RESOURCE_ID
                                 ? container.findViewById(mDividerStartId)
                                 : container;
 
                 View endChild =
-                        mDvidierEndId != INVALID_RESOURCE_ID
-                                ? container.findViewById(mDvidierEndId)
+                        mDividerEndId != INVALID_RESOURCE_ID
+                                ? container.findViewById(mDividerEndId)
                                 : container;
 
                 if (startChild == null || endChild == null) {
@@ -825,14 +1004,24 @@
 
                 int left = mDividerStartMargin + startChild.getLeft();
                 int right = endChild.getRight();
-                int bottom = container.getBottom();
+                int bottom = container.getBottom() + spacing / 2 + mDividerHeight / 2;
                 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);
-                }
+                c.drawRect(left, top, right, bottom, mPaint);
+            }
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            // Skip top offset for first item and bottom offset for last.
+            int position = parent.getChildAdapterPosition(view);
+            if (position > 0) {
+                outRect.top = mDividerHeight / 2;
+            }
+            if (position < state.getItemCount() - 1) {
+                outRect.bottom = mDividerHeight / 2;
             }
         }
     }
diff --git a/android/support/car/widget/PagedScrollBarView.java b/android/support/car/widget/PagedScrollBarView.java
index 125b354..1c46b5d 100644
--- a/android/support/car/widget/PagedScrollBarView.java
+++ b/android/support/car/widget/PagedScrollBarView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
 import android.support.car.R;
 import android.support.v4.content.ContextCompat;
 import android.util.AttributeSet;
@@ -98,6 +99,16 @@
         return true;
     }
 
+    /** Sets the icon to be used for the up button. */
+    public void setUpButtonIcon(Drawable icon) {
+        mUpButton.setImageDrawable(icon);
+    }
+
+    /** Sets the icon to be used for the down button. */
+    public void setDownButtonIcon(Drawable icon) {
+        mDownButton.setImageDrawable(icon);
+    }
+
     /**
      * Sets the listener that will be notified when the up and down buttons have been pressed.
      *
@@ -119,7 +130,7 @@
 
     /** 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
+        // This method is where we take the computed parameters from the PagedLayoutManager and
         // render it within the specified constraints ({@link #mMaxThumbLength} and
         // {@link #mMinThumbLength}).
         final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
deleted file mode 100644
index a18bcf3..0000000
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.testlib;
-
-/**
- * Constants used for sending intent between client and service apps.
- */
-public class IntentConstants {
-    public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
-            "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
-    public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
-            "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
-    public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
-            "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
-    public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
-            "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
-    public static final String KEY_METHOD_ID = "method_id";
-    public static final String KEY_ARGUMENT = "argument";
-    public static final String KEY_SESSION_TOKEN = "session_token";
-}
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
index 95be162..cbdccc1 100644
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -40,7 +40,6 @@
     public static final int SET_RATING_TYPE = 117;
 
     public static final String TEST_SESSION_TAG = "test-session-tag";
-    public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
     public static final String TEST_KEY = "test-key";
     public static final String TEST_VALUE = "test-val";
     public static final String TEST_SESSION_EVENT = "test-session-event";
diff --git a/android/support/mediacompat/testlib/VersionConstants.java b/android/support/mediacompat/testlib/VersionConstants.java
new file mode 100644
index 0000000..6533ee1
--- /dev/null
+++ b/android/support/mediacompat/testlib/VersionConstants.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib;
+
+/**
+ * Constants for getting support library version information.
+ */
+public class VersionConstants {
+    public static final String KEY_CLIENT_VERSION = "client_version";
+    public static final String KEY_SERVICE_VERSION = "service_version";
+}
diff --git a/android/support/mediacompat/testlib/util/IntentUtil.java b/android/support/mediacompat/testlib/util/IntentUtil.java
new file mode 100644
index 0000000..bbf9752
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/IntentUtil.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+/**
+ * Methods and constants used for sending intent between client and service apps.
+ */
+public class IntentUtil {
+
+    public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName(
+            "android.support.mediacompat.service.test",
+            "android.support.mediacompat.service.ServiceBroadcastReceiver");
+    public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName(
+            "android.support.mediacompat.client.test",
+            "android.support.mediacompat.client.ClientBroadcastReceiver");
+
+    public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
+            "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
+    public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
+            "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
+    public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
+            "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
+    public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
+            "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
+
+    public static final String KEY_METHOD_ID = "method_id";
+    public static final String KEY_ARGUMENT = "argument";
+    public static final String KEY_SESSION_TOKEN = "session_token";
+
+    /**
+     * Calls a method of MediaBrowserService. Used by client app.
+     */
+    public static void callMediaBrowserServiceMethod(int methodId, Object arg, Context context) {
+        Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    /**
+     * Calls a method of MediaSession. Used by client app.
+     */
+    public static void callMediaSessionMethod(int methodId, Object arg, Context context) {
+        Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_MEDIA_SESSION_METHOD);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    /**
+     * Calls a method of MediaController. Used by service app.
+     */
+    public static void callMediaControllerMethod(
+            int methodId, Object arg, Context context, Parcelable token) {
+        Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_MEDIA_CONTROLLER_METHOD);
+        intent.putExtra(KEY_SESSION_TOKEN, token);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    /**
+     * Calls a method of TransportControls. Used by service app.
+     */
+    public static void callTransportControlsMethod(
+            int methodId, Object arg, Context context, Parcelable token) {
+        Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_TRANSPORT_CONTROLS_METHOD);
+        intent.putExtra(KEY_SESSION_TOKEN, token);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    private static Intent createIntent(ComponentName componentName, int methodId, Object arg) {
+        Intent intent = new Intent();
+        intent.setComponent(componentName);
+        intent.putExtra(KEY_METHOD_ID, methodId);
+
+        if (arg instanceof String) {
+            intent.putExtra(KEY_ARGUMENT, (String) arg);
+        } else if (arg instanceof Integer) {
+            intent.putExtra(KEY_ARGUMENT, (int) arg);
+        } else if (arg instanceof Long) {
+            intent.putExtra(KEY_ARGUMENT, (long) arg);
+        } else if (arg instanceof Boolean) {
+            intent.putExtra(KEY_ARGUMENT, (boolean) arg);
+        } else if (arg instanceof Parcelable) {
+            intent.putExtra(KEY_ARGUMENT, (Parcelable) arg);
+        } else if (arg instanceof ArrayList<?>) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(KEY_ARGUMENT, (ArrayList<? extends Parcelable>) arg);
+            intent.putExtras(bundle);
+        } else if (arg instanceof Bundle) {
+            Bundle bundle = new Bundle();
+            bundle.putBundle(KEY_ARGUMENT, (Bundle) arg);
+            intent.putExtras(bundle);
+        }
+        return intent;
+    }
+}
diff --git a/android/support/mediacompat/testlib/util/PollingCheck.java b/android/support/mediacompat/testlib/util/PollingCheck.java
new file mode 100644
index 0000000..3412da0
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/PollingCheck.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import junit.framework.Assert;
+
+/**
+ * Utility used for testing that allows to poll for a certain condition to happen within a timeout.
+ * (Copied from testutils/src/main/java/android/support/testutils/PollingCheck.java.)
+ */
+public abstract class PollingCheck {
+    private static final long DEFAULT_TIMEOUT = 3000;
+    private static final long TIME_SLICE = 50;
+    private final long mTimeout;
+
+    /**
+     * The condition that the PollingCheck should use to proceed successfully.
+     */
+    public interface PollingCheckCondition {
+        /**
+         * @return Whether the polling condition has been met.
+         */
+        boolean canProceed();
+    }
+
+    public PollingCheck(long timeout) {
+        mTimeout = timeout;
+    }
+
+    protected abstract boolean check();
+
+    /**
+     * Start running the polling check.
+     */
+    public void run() {
+        if (check()) {
+            return;
+        }
+
+        long timeout = mTimeout;
+        while (timeout > 0) {
+            try {
+                Thread.sleep(TIME_SLICE);
+            } catch (InterruptedException e) {
+                Assert.fail("unexpected InterruptedException");
+            }
+
+            if (check()) {
+                return;
+            }
+
+            timeout -= TIME_SLICE;
+        }
+
+        Assert.fail("unexpected timeout");
+    }
+
+    /**
+     * Instantiate and start polling for a given condition with a default 3000ms timeout.
+     * @param condition The condition to check for success.
+     */
+    public static void waitFor(final PollingCheckCondition condition) {
+        new PollingCheck(DEFAULT_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return condition.canProceed();
+            }
+        }.run();
+    }
+
+    /**
+     * Instantiate and start polling for a given condition.
+     * @param timeout Time out in ms
+     * @param condition The condition to check for success.
+     */
+    public static void waitFor(long timeout, final PollingCheckCondition condition) {
+        new PollingCheck(timeout) {
+            @Override
+            protected boolean check() {
+                return condition.canProceed();
+            }
+        }.run();
+    }
+}
diff --git a/android/support/mediacompat/testlib/util/TestUtil.java b/android/support/mediacompat/testlib/util/TestUtil.java
new file mode 100644
index 0000000..d105510
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/TestUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+
+import android.os.Bundle;
+
+/**
+ * Utility methods used for testing.
+ */
+public final class TestUtil {
+
+    /**
+     * Asserts that two Bundles are equal.
+     */
+    public static void assertBundleEquals(Bundle expected, Bundle observed) {
+        if (expected == null || observed == null) {
+            assertSame(expected, observed);
+        }
+        assertEquals(expected.size(), observed.size());
+        for (String key : expected.keySet()) {
+            assertEquals(expected.get(key), observed.get(key));
+        }
+    }
+}
diff --git a/android/support/text/emoji/widget/EmojiAppCompatEditText.java b/android/support/text/emoji/widget/EmojiAppCompatEditText.java
index 87c17c2..0ae4ea0 100644
--- a/android/support/text/emoji/widget/EmojiAppCompatEditText.java
+++ b/android/support/text/emoji/widget/EmojiAppCompatEditText.java
@@ -21,6 +21,7 @@
 import android.support.annotation.Nullable;
 import android.support.text.emoji.EmojiCompat;
 import android.support.v7.widget.AppCompatEditText;
+import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -67,8 +68,11 @@
     }
 
     @Override
-    public void setKeyListener(android.text.method.KeyListener input) {
-        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input));
+    public void setKeyListener(@Nullable KeyListener keyListener) {
+        if (keyListener != null) {
+            keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+        }
+        super.setKeyListener(keyListener);
     }
 
     @Override
diff --git a/android/support/text/emoji/widget/EmojiEditText.java b/android/support/text/emoji/widget/EmojiEditText.java
index a0e8a69..70ca7a6 100644
--- a/android/support/text/emoji/widget/EmojiEditText.java
+++ b/android/support/text/emoji/widget/EmojiEditText.java
@@ -21,6 +21,7 @@
 import android.support.annotation.IntRange;
 import android.support.annotation.Nullable;
 import android.support.text.emoji.EmojiCompat;
+import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -73,8 +74,11 @@
     }
 
     @Override
-    public void setKeyListener(android.text.method.KeyListener keyListener) {
-        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+    public void setKeyListener(@Nullable KeyListener keyListener) {
+        if (keyListener != null) {
+            keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+        }
+        super.setKeyListener(keyListener);
     }
 
     @Override
diff --git a/android/support/text/emoji/widget/EmojiExtractEditText.java b/android/support/text/emoji/widget/EmojiExtractEditText.java
index ca1868e..2e4d3ca 100644
--- a/android/support/text/emoji/widget/EmojiExtractEditText.java
+++ b/android/support/text/emoji/widget/EmojiExtractEditText.java
@@ -27,6 +27,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.text.emoji.EmojiCompat;
 import android.support.text.emoji.EmojiSpan;
+import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -81,8 +82,11 @@
     }
 
     @Override
-    public void setKeyListener(android.text.method.KeyListener keyListener) {
-        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+    public void setKeyListener(@Nullable KeyListener keyListener) {
+        if (keyListener != null) {
+            keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+        }
+        super.setKeyListener(keyListener);
     }
 
     @Override
diff --git a/android/support/transition/Transition.java b/android/support/transition/Transition.java
index 04cc57b..9c198a9 100644
--- a/android/support/transition/Transition.java
+++ b/android/support/transition/Transition.java
@@ -1017,7 +1017,7 @@
      */
     @NonNull
     public Transition addTarget(@IdRes int targetId) {
-        if (targetId > 0) {
+        if (targetId != 0) {
             mTargetIds.add(targetId);
         }
         return this;
@@ -1107,7 +1107,7 @@
      */
     @NonNull
     public Transition removeTarget(@IdRes int targetId) {
-        if (targetId > 0) {
+        if (targetId != 0) {
             mTargetIds.remove((Integer) targetId);
         }
         return this;
diff --git a/android/support/v17/leanback/app/BaseFragment.java b/android/support/v17/leanback/app/BaseFragment.java
index bdb213f..ea46011 100644
--- a/android/support/v17/leanback/app/BaseFragment.java
+++ b/android/support/v17/leanback/app/BaseFragment.java
@@ -31,7 +31,9 @@
 
 /**
  * Base class for leanback Fragments. This class is not intended to be subclassed by apps.
+ * @deprecated use {@link BaseSupportFragment}
  */
+@Deprecated
 @SuppressWarnings("FragmentNotInstantiable")
 public class BaseFragment extends BrandedFragment {
 
diff --git a/android/support/v17/leanback/app/BaseRowFragment.java b/android/support/v17/leanback/app/BaseRowFragment.java
index 2d79f3e..97a5b84 100644
--- a/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/android/support/v17/leanback/app/BaseRowFragment.java
@@ -34,7 +34,9 @@
 
 /**
  * An internal base class for a fragment containing a list of rows.
+ * @deprecated use {@link BaseRowSupportFragment}
  */
+@Deprecated
 abstract class BaseRowFragment extends Fragment {
     private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
     private ObjectAdapter mAdapter;
@@ -164,8 +166,10 @@
      * Set the presenter selector used to create and bind views.
      */
     public final void setPresenterSelector(PresenterSelector presenterSelector) {
-        mPresenterSelector = presenterSelector;
-        updateAdapter();
+        if (mPresenterSelector != presenterSelector) {
+            mPresenterSelector = presenterSelector;
+            updateAdapter();
+        }
     }
 
     /**
@@ -180,8 +184,10 @@
      * @param rowsAdapter Adapter that represents list of rows.
      */
     public final void setAdapter(ObjectAdapter rowsAdapter) {
-        mAdapter = rowsAdapter;
-        updateAdapter();
+        if (mAdapter != rowsAdapter) {
+            mAdapter = rowsAdapter;
+            updateAdapter();
+        }
     }
 
     /**
diff --git a/android/support/v17/leanback/app/BaseRowSupportFragment.java b/android/support/v17/leanback/app/BaseRowSupportFragment.java
index dba78da..6a477ab 100644
--- a/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -161,8 +161,10 @@
      * Set the presenter selector used to create and bind views.
      */
     public final void setPresenterSelector(PresenterSelector presenterSelector) {
-        mPresenterSelector = presenterSelector;
-        updateAdapter();
+        if (mPresenterSelector != presenterSelector) {
+            mPresenterSelector = presenterSelector;
+            updateAdapter();
+        }
     }
 
     /**
@@ -177,8 +179,10 @@
      * @param rowsAdapter Adapter that represents list of rows.
      */
     public final void setAdapter(ObjectAdapter rowsAdapter) {
-        mAdapter = rowsAdapter;
-        updateAdapter();
+        if (mAdapter != rowsAdapter) {
+            mAdapter = rowsAdapter;
+            updateAdapter();
+        }
     }
 
     /**
diff --git a/android/support/v17/leanback/app/BrandedFragment.java b/android/support/v17/leanback/app/BrandedFragment.java
index 1f6ad29..415c13e 100644
--- a/android/support/v17/leanback/app/BrandedFragment.java
+++ b/android/support/v17/leanback/app/BrandedFragment.java
@@ -33,7 +33,9 @@
 /**
  * Fragment class for managing search and branding using a view that implements
  * {@link TitleViewAdapter.Provider}.
+ * @deprecated use {@link BrandedSupportFragment}
  */
+@Deprecated
 public class BrandedFragment extends Fragment {
 
     // BUNDLE attribute for title is showing
diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java
index ae31c4f..c561ea9 100644
--- a/android/support/v17/leanback/app/BrowseFragment.java
+++ b/android/support/v17/leanback/app/BrowseFragment.java
@@ -81,7 +81,9 @@
  * The recommended theme to use with a BrowseFragment is
  * {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
  * </p>
+ * @deprecated use {@link BrowseSupportFragment}
  */
+@Deprecated
 public class BrowseFragment extends BaseFragment {
 
     // BUNDLE attribute for saving header show/hide status when backstack is used:
@@ -203,7 +205,9 @@
 
     /**
      * Listener for transitions between browse headers and rows.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public static class BrowseTransitionListener {
         /**
          * Callback when headers transition starts.
@@ -267,7 +271,9 @@
     /**
      * Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
      * fragments can interact with {@link BrowseFragment} using this interface.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public interface FragmentHost {
         /**
          * Fragments are required to invoke this callback once their view is created
@@ -376,7 +382,9 @@
      * and provide that through {@link MainFragmentAdapterRegistry}.
      * {@link MainFragmentAdapter} implementation can supply any fragment and override
      * just those interactions that makes sense.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public static class MainFragmentAdapter<T extends Fragment> {
         private boolean mScalingEnabled;
         private final T mFragment;
@@ -466,7 +474,9 @@
      * Interface to be implemented by all fragments for providing an instance of
      * {@link MainFragmentAdapter}. Both {@link RowsFragment} and custom fragment provided
      * against {@link PageRow} will need to implement this interface.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public interface MainFragmentAdapterProvider {
         /**
          * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseFragment}
@@ -478,7 +488,9 @@
     /**
      * Interface to be implemented by {@link RowsFragment} and its subclasses for providing
      * an instance of {@link MainFragmentRowsAdapter}.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public interface MainFragmentRowsAdapterProvider {
         /**
          * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseFragment}
@@ -491,7 +503,9 @@
      * This is used to pass information to {@link RowsFragment} or its subclasses.
      * {@link BrowseFragment} uses this interface to pass row based interaction events to
      * the target fragment.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public static class MainFragmentRowsAdapter<T extends Fragment> {
         private final T mFragment;
 
@@ -570,14 +584,27 @@
         }
 
         boolean oldIsPageRow = mIsPageRow;
+        Object oldPageRow = mPageRow;
         mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+        mPageRow = mIsPageRow ? item : null;
         boolean swap;
 
         if (mMainFragment == null) {
             swap = true;
         } else {
             if (oldIsPageRow) {
-                swap = true;
+                if (mIsPageRow) {
+                    if (oldPageRow == null) {
+                        // fragment is restored, page row object not yet set, so just set the
+                        // mPageRow object and there is no need to replace the fragment
+                        swap = false;
+                    } else {
+                        // swap if page row object changes
+                        swap = oldPageRow != mPageRow;
+                    }
+                } else {
+                    swap = true;
+                }
             } else {
                 swap = mIsPageRow;
             }
@@ -590,37 +617,45 @@
                         "Fragment must implement MainFragmentAdapterProvider");
             }
 
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-                mIsPageRow = mMainFragmentRowsAdapter == null;
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         return swap;
     }
 
+    void setMainFragmentAdapter() {
+        mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+                .getMainFragmentAdapter();
+        mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+        if (!mIsPageRow) {
+            if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+                setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+                        .getMainFragmentRowsAdapter());
+            } else {
+                setMainFragmentRowsAdapter(null);
+            }
+            mIsPageRow = mMainFragmentRowsAdapter == null;
+        } else {
+            setMainFragmentRowsAdapter(null);
+        }
+    }
+
     /**
      * Factory class responsible for creating fragment given the current item. {@link ListRow}
      * should return {@link RowsFragment} or its subclass whereas {@link PageRow}
      * can return any fragment class.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public abstract static class FragmentFactory<T extends Fragment> {
         public abstract T createFragment(Object row);
     }
 
     /**
      * FragmentFactory implementation for {@link ListRow}.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> {
         @Override
         public RowsFragment createFragment(Object row) {
@@ -634,7 +669,9 @@
      * handling {@link ListRow}. Developers can override that and also if they want to
      * use custom fragment, they can register a custom {@link FragmentFactory}
      * against {@link PageRow}.
+     * @deprecated use {@link BrowseSupportFragment}
      */
+    @Deprecated
     public final static class MainFragmentAdapterRegistry {
         private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>();
         private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
@@ -678,7 +715,8 @@
     MainFragmentAdapter mMainFragmentAdapter;
     Fragment mMainFragment;
     HeadersFragment mHeadersFragment;
-    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    ListRowDataAdapter mMainFragmentListRowDataAdapter;
 
     private ObjectAdapter mAdapter;
     private PresenterSelector mAdapterPresenter;
@@ -701,6 +739,7 @@
     private int mSelectedPosition = -1;
     private float mScaleFactor;
     boolean mIsPageRow;
+    Object mPageRow;
 
     private PresenterSelector mHeaderPresenterSelector;
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -820,11 +859,45 @@
             return;
         }
 
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setAdapter(
-                    adapter == null ? null : new ListRowDataAdapter(adapter));
+        updateMainFragmentRowsAdapter();
+        mHeadersFragment.setAdapter(mAdapter);
+    }
+
+    void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+        if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+            return;
         }
-        mHeadersFragment.setAdapter(adapter);
+        // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
+        if (mMainFragmentRowsAdapter != null) {
+            // RowsFragment cannot change click/select listeners after view created.
+            // The main fragment and adapter should be GCed as long as there is no reference from
+            // BrowseFragment to it.
+            mMainFragmentRowsAdapter.setAdapter(null);
+        }
+        mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+        // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+        updateMainFragmentRowsAdapter();
+    }
+
+    /**
+     * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+     * It also clears old mMainFragmentListRowDataAdapter.
+     */
+    void updateMainFragmentRowsAdapter() {
+        if (mMainFragmentListRowDataAdapter != null) {
+            mMainFragmentListRowDataAdapter.detach();
+            mMainFragmentListRowDataAdapter = null;
+        }
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentListRowDataAdapter = mAdapter == null
+                    ? null : new ListRowDataAdapter(mAdapter);
+            mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
+        }
     }
 
     public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1144,7 +1217,8 @@
 
     @Override
     public void onDestroyView() {
-        mMainFragmentRowsAdapter = null;
+        setMainFragmentRowsAdapter(null);
+        mPageRow = null;
         mMainFragmentAdapter = null;
         mMainFragment = null;
         mHeadersFragment = null;
@@ -1198,26 +1272,17 @@
             mHeadersFragment = (HeadersFragment) getChildFragmentManager()
                     .findFragmentById(R.id.browse_headers_dock);
             mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
 
             mIsPageRow = savedInstanceState != null
                     && savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+            // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+            // the case for restoring, later if setSelection() triggers a createMainFragment(),
+            // should not create fragment.
 
             mSelectedPosition = savedInstanceState != null
                     ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
 
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         mHeadersFragment.setHeadersGone(!mCanShowHeaders);
@@ -1242,8 +1307,6 @@
         mScaleFrameLayout.setPivotX(0);
         mScaleFrameLayout.setPivotY(mContainerListAlignTop);
 
-        setupMainFragment();
-
         if (mBrandColorSet) {
             mHeadersFragment.setBackgroundColor(mBrandColor);
         }
@@ -1270,17 +1333,6 @@
         return root;
     }
 
-    private void setupMainFragment() {
-        if (mMainFragmentRowsAdapter != null) {
-            if (mAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
-            }
-            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
-                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
-            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        }
-    }
-
     void createHeadersTransition() {
         mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
                 mShowingHeaders
@@ -1470,10 +1522,10 @@
     };
 
     void onRowSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSetSelectionRunnable.post(
-                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-        }
+        // even position is same, it could be data changed, always post selection runnable
+        // to possibly swap main fragment.
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
     }
 
     void setSelection(int position, boolean smooth) {
@@ -1500,7 +1552,6 @@
         if (createMainFragment(mAdapter, position)) {
             swapToMainFragment();
             expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
-            setupMainFragment();
         }
     }
 
diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java
index 4a2502a..c28064c 100644
--- a/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -567,14 +567,27 @@
         }
 
         boolean oldIsPageRow = mIsPageRow;
+        Object oldPageRow = mPageRow;
         mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+        mPageRow = mIsPageRow ? item : null;
         boolean swap;
 
         if (mMainFragment == null) {
             swap = true;
         } else {
             if (oldIsPageRow) {
-                swap = true;
+                if (mIsPageRow) {
+                    if (oldPageRow == null) {
+                        // fragment is restored, page row object not yet set, so just set the
+                        // mPageRow object and there is no need to replace the fragment
+                        swap = false;
+                    } else {
+                        // swap if page row object changes
+                        swap = oldPageRow != mPageRow;
+                    }
+                } else {
+                    swap = true;
+                }
             } else {
                 swap = mIsPageRow;
             }
@@ -587,25 +600,29 @@
                         "Fragment must implement MainFragmentAdapterProvider");
             }
 
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-                mIsPageRow = mMainFragmentRowsAdapter == null;
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         return swap;
     }
 
+    void setMainFragmentAdapter() {
+        mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+                .getMainFragmentAdapter();
+        mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+        if (!mIsPageRow) {
+            if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+                setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+                        .getMainFragmentRowsAdapter());
+            } else {
+                setMainFragmentRowsAdapter(null);
+            }
+            mIsPageRow = mMainFragmentRowsAdapter == null;
+        } else {
+            setMainFragmentRowsAdapter(null);
+        }
+    }
+
     /**
      * Factory class responsible for creating fragment given the current item. {@link ListRow}
      * should return {@link RowsSupportFragment} or its subclass whereas {@link PageRow}
@@ -675,7 +692,8 @@
     MainFragmentAdapter mMainFragmentAdapter;
     Fragment mMainFragment;
     HeadersSupportFragment mHeadersSupportFragment;
-    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    ListRowDataAdapter mMainFragmentListRowDataAdapter;
 
     private ObjectAdapter mAdapter;
     private PresenterSelector mAdapterPresenter;
@@ -698,6 +716,7 @@
     private int mSelectedPosition = -1;
     private float mScaleFactor;
     boolean mIsPageRow;
+    Object mPageRow;
 
     private PresenterSelector mHeaderPresenterSelector;
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -817,11 +836,45 @@
             return;
         }
 
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setAdapter(
-                    adapter == null ? null : new ListRowDataAdapter(adapter));
+        updateMainFragmentRowsAdapter();
+        mHeadersSupportFragment.setAdapter(mAdapter);
+    }
+
+    void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+        if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+            return;
         }
-        mHeadersSupportFragment.setAdapter(adapter);
+        // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
+        if (mMainFragmentRowsAdapter != null) {
+            // RowsFragment cannot change click/select listeners after view created.
+            // The main fragment and adapter should be GCed as long as there is no reference from
+            // BrowseSupportFragment to it.
+            mMainFragmentRowsAdapter.setAdapter(null);
+        }
+        mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+        // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+        updateMainFragmentRowsAdapter();
+    }
+
+    /**
+     * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+     * It also clears old mMainFragmentListRowDataAdapter.
+     */
+    void updateMainFragmentRowsAdapter() {
+        if (mMainFragmentListRowDataAdapter != null) {
+            mMainFragmentListRowDataAdapter.detach();
+            mMainFragmentListRowDataAdapter = null;
+        }
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentListRowDataAdapter = mAdapter == null
+                    ? null : new ListRowDataAdapter(mAdapter);
+            mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
+        }
     }
 
     public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1141,7 +1194,8 @@
 
     @Override
     public void onDestroyView() {
-        mMainFragmentRowsAdapter = null;
+        setMainFragmentRowsAdapter(null);
+        mPageRow = null;
         mMainFragmentAdapter = null;
         mMainFragment = null;
         mHeadersSupportFragment = null;
@@ -1195,26 +1249,17 @@
             mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
                     .findFragmentById(R.id.browse_headers_dock);
             mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
 
             mIsPageRow = savedInstanceState != null
                     && savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+            // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+            // the case for restoring, later if setSelection() triggers a createMainFragment(),
+            // should not create fragment.
 
             mSelectedPosition = savedInstanceState != null
                     ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
 
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
@@ -1239,8 +1284,6 @@
         mScaleFrameLayout.setPivotX(0);
         mScaleFrameLayout.setPivotY(mContainerListAlignTop);
 
-        setupMainFragment();
-
         if (mBrandColorSet) {
             mHeadersSupportFragment.setBackgroundColor(mBrandColor);
         }
@@ -1267,17 +1310,6 @@
         return root;
     }
 
-    private void setupMainFragment() {
-        if (mMainFragmentRowsAdapter != null) {
-            if (mAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
-            }
-            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
-                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
-            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        }
-    }
-
     void createHeadersTransition() {
         mHeadersTransition = TransitionHelper.loadTransition(getContext(),
                 mShowingHeaders
@@ -1467,10 +1499,10 @@
     };
 
     void onRowSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSetSelectionRunnable.post(
-                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-        }
+        // even position is same, it could be data changed, always post selection runnable
+        // to possibly swap main fragment.
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
     }
 
     void setSelection(int position, boolean smooth) {
@@ -1497,7 +1529,6 @@
         if (createMainFragment(mAdapter, position)) {
             swapToMainFragment();
             expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
-            setupMainFragment();
         }
     }
 
diff --git a/android/support/v17/leanback/app/DetailsFragment.java b/android/support/v17/leanback/app/DetailsFragment.java
index 3655963..18934f4 100644
--- a/android/support/v17/leanback/app/DetailsFragment.java
+++ b/android/support/v17/leanback/app/DetailsFragment.java
@@ -91,7 +91,9 @@
  * DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable
  * background and embedded video playing fragment.
  * </p>
+ * @deprecated use {@link DetailsSupportFragment}
  */
+@Deprecated
 public class DetailsFragment extends BaseFragment {
     static final String TAG = "DetailsFragment";
     static boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
index 223b8ef..25ed723 100644
--- a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
+++ b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
@@ -107,7 +107,9 @@
  * {@link #onCreateGlueHost()}.
  * </p>
  *
+ * @deprecated use {@link DetailsSupportFragmentBackgroundController}
  */
+@Deprecated
 public class DetailsFragmentBackgroundController {
 
     final DetailsFragment mFragment;
diff --git a/android/support/v17/leanback/app/ErrorFragment.java b/android/support/v17/leanback/app/ErrorFragment.java
index 2896d0f..eda0de1 100644
--- a/android/support/v17/leanback/app/ErrorFragment.java
+++ b/android/support/v17/leanback/app/ErrorFragment.java
@@ -32,7 +32,9 @@
 
 /**
  * A fragment for displaying an error indication.
+ * @deprecated use {@link ErrorSupportFragment}
  */
+@Deprecated
 public class ErrorFragment extends BrandedFragment {
 
     private ViewGroup mErrorFrame;
diff --git a/android/support/v17/leanback/app/GuidedStepFragment.java b/android/support/v17/leanback/app/GuidedStepFragment.java
index 2b7f2d0..9be350d 100644
--- a/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -27,6 +27,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
 import android.support.v17.leanback.widget.GuidanceStylist;
 import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
 import android.support.v17.leanback.widget.GuidedAction;
@@ -140,7 +141,9 @@
  * @see GuidanceStylist.Guidance
  * @see GuidedAction
  * @see GuidedActionsStylist
+ * @deprecated use {@link GuidedStepSupportFragment}
  */
+@Deprecated
 public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener {
 
     private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
@@ -806,6 +809,8 @@
 
     /**
      * Sets the list of GuidedActions that the user may take in this fragment.
+     * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+     *
      * @param actions The list of GuidedActions for this fragment.
      */
     public void setActions(List<GuidedAction> actions) {
@@ -816,6 +821,18 @@
     }
 
     /**
+     * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+     * GuidedStepFragment uses
+     * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+     * Sets it to null if app wants to refresh the whole list.
+     *
+     * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+     */
+    public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+        mAdapter.setDiffCallback(diffCallback);
+    }
+
+    /**
      * Notify an action has changed and update its UI.
      * @param position Position of the GuidedAction in array.
      */
diff --git a/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index aeb2d33..e276d07 100644
--- a/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -24,6 +24,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
 import android.support.v17.leanback.widget.GuidanceStylist;
 import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
 import android.support.v17.leanback.widget.GuidedAction;
@@ -803,6 +804,8 @@
 
     /**
      * Sets the list of GuidedActions that the user may take in this fragment.
+     * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+     *
      * @param actions The list of GuidedActions for this fragment.
      */
     public void setActions(List<GuidedAction> actions) {
@@ -813,6 +816,18 @@
     }
 
     /**
+     * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+     * GuidedStepSupportFragment uses
+     * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+     * Sets it to null if app wants to refresh the whole list.
+     *
+     * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+     */
+    public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+        mAdapter.setDiffCallback(diffCallback);
+    }
+
+    /**
      * Notify an action has changed and update its UI.
      * @param position Position of the GuidedAction in array.
      */
diff --git a/android/support/v17/leanback/app/HeadersFragment.java b/android/support/v17/leanback/app/HeadersFragment.java
index dd037d2..08780a5 100644
--- a/android/support/v17/leanback/app/HeadersFragment.java
+++ b/android/support/v17/leanback/app/HeadersFragment.java
@@ -52,12 +52,16 @@
  * </ul>
  * Use {@link #setPresenterSelector(PresenterSelector)} in subclass constructor to customize
  * Presenters. App may override {@link BrowseFragment#onCreateHeadersFragment()}.
+ * @deprecated use {@link HeadersSupportFragment}
  */
+@Deprecated
 public class HeadersFragment extends BaseRowFragment {
 
     /**
      * Interface definition for a callback to be invoked when a header item is clicked.
+     * @deprecated use {@link HeadersSupportFragment}
      */
+    @Deprecated
     public interface OnHeaderClickedListener {
         /**
          * Called when a header item has been clicked.
@@ -70,7 +74,9 @@
 
     /**
      * Interface definition for a callback to be invoked when a header item is selected.
+     * @deprecated use {@link HeadersSupportFragment}
      */
+    @Deprecated
     public interface OnHeaderViewSelectedListener {
         /**
          * Called when a header item has been selected.
diff --git a/android/support/v17/leanback/app/ListRowDataAdapter.java b/android/support/v17/leanback/app/ListRowDataAdapter.java
index f9af12f..03d948b 100644
--- a/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ b/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -13,6 +13,7 @@
  * thinks there are items even though they're invisible. This class takes care of filtering out
  * the invisible rows at the end. In case the data inside the adapter changes, it adjusts the
  * bounds to reflect the latest data.
+ * {@link #detach()} must be called to release DataObserver from Adapter.
  */
 class ListRowDataAdapter extends ObjectAdapter {
     public static final int ON_ITEM_RANGE_CHANGED = 2;
@@ -22,6 +23,7 @@
 
     private final ObjectAdapter mAdapter;
     int mLastVisibleRowIndex;
+    final DataObserver mDataObserver;
 
     public ListRowDataAdapter(ObjectAdapter adapter) {
         super(adapter.getPresenterSelector());
@@ -34,10 +36,20 @@
         // operation. To handle this case, we use QueueBasedDataObserver which forces
         // recyclerview to do a full data refresh after each update operation.
         if (adapter.isImmediateNotifySupported()) {
-            mAdapter.registerObserver(new SimpleDataObserver());
+            mDataObserver = new SimpleDataObserver();
         } else {
-            mAdapter.registerObserver(new QueueBasedDataObserver());
+            mDataObserver = new QueueBasedDataObserver();
         }
+        attach();
+    }
+
+    void detach() {
+        mAdapter.unregisterObserver(mDataObserver);
+    }
+
+    void attach() {
+        initialize();
+        mAdapter.registerObserver(mDataObserver);
     }
 
     void initialize() {
diff --git a/android/support/v17/leanback/app/OnboardingFragment.java b/android/support/v17/leanback/app/OnboardingFragment.java
index b69d5a7..f352c41 100644
--- a/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/android/support/v17/leanback/app/OnboardingFragment.java
@@ -154,7 +154,9 @@
  * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
  * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
  * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
+ * @deprecated use {@link OnboardingSupportFragment}
  */
+@Deprecated
 abstract public class OnboardingFragment extends Fragment {
     private static final String TAG = "OnboardingF";
     private static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/PlaybackFragment.java b/android/support/v17/leanback/app/PlaybackFragment.java
index 33e787c..e2e6be4 100644
--- a/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/android/support/v17/leanback/app/PlaybackFragment.java
@@ -81,7 +81,9 @@
  * {@link #setControlsOverlayAutoHideEnabled(boolean)} upon play/pause. The auto hiding timer will
  * be cancelled upon {@link #tickle()} triggered by input event.
  * </p>
+ * @deprecated use {@link PlaybackSupportFragment}
  */
+@Deprecated
 public class PlaybackFragment extends Fragment {
     static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
 
@@ -181,7 +183,9 @@
      * Listener allowing the application to receive notification of fade in and/or fade out
      * completion events.
      * @hide
+     * @deprecated use {@link PlaybackSupportFragment}
      */
+    @Deprecated
     public static class OnFadeCompleteListener {
         public void onFadeInComplete() {
         }
diff --git a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
index 4a9d10f..9e342fd 100644
--- a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -30,7 +30,9 @@
 /**
  * {@link PlaybackGlueHost} implementation
  * the interaction between this class and {@link PlaybackFragment}.
+ * @deprecated use {@link PlaybackSupportFragmentGlueHost}
  */
+@Deprecated
 public class PlaybackFragmentGlueHost extends PlaybackGlueHost implements PlaybackSeekUi {
     private final PlaybackFragment mFragment;
 
diff --git a/android/support/v17/leanback/app/RowsFragment.java b/android/support/v17/leanback/app/RowsFragment.java
index a008ad6..aa346bd 100644
--- a/android/support/v17/leanback/app/RowsFragment.java
+++ b/android/support/v17/leanback/app/RowsFragment.java
@@ -53,7 +53,9 @@
  * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
  * of {@link RowPresenter}.
  * </p>
+ * @deprecated use {@link RowsSupportFragment}
  */
+@Deprecated
 public class RowsFragment extends BaseRowFragment implements
         BrowseFragment.MainFragmentRowsAdapterProvider,
         BrowseFragment.MainFragmentAdapterProvider {
@@ -634,7 +636,9 @@
      * The adapter that RowsFragment implements
      * BrowseFragment.MainFragmentRowsAdapter.
      * @see #getMainFragmentRowsAdapter().
+     * @deprecated use {@link RowsSupportFragment}
      */
+    @Deprecated
     public static class MainFragmentRowsAdapter
             extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
 
diff --git a/android/support/v17/leanback/app/SearchFragment.java b/android/support/v17/leanback/app/SearchFragment.java
index 2154ff2..00f2cca 100644
--- a/android/support/v17/leanback/app/SearchFragment.java
+++ b/android/support/v17/leanback/app/SearchFragment.java
@@ -66,7 +66,9 @@
  * not when fragment is restored from an instance state.  Activity may manually
  * call {@link #startRecognition()}, typically in onNewIntent().
  * </p>
+ * @deprecated use {@link SearchSupportFragment}
  */
+@Deprecated
 public class SearchFragment extends Fragment {
     static final String TAG = SearchFragment.class.getSimpleName();
     static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/VerticalGridFragment.java b/android/support/v17/leanback/app/VerticalGridFragment.java
index 5bc52ff..bff3dba 100644
--- a/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -39,7 +39,9 @@
  *
  * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
  * an {@link ObjectAdapter}.
+ * @deprecated use {@link VerticalGridSupportFragment}
  */
+@Deprecated
 public class VerticalGridFragment extends BaseFragment {
     static final String TAG = "VerticalGF";
     static boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/VideoFragment.java b/android/support/v17/leanback/app/VideoFragment.java
index 1b2b8d0..e4d75f3 100644
--- a/android/support/v17/leanback/app/VideoFragment.java
+++ b/android/support/v17/leanback/app/VideoFragment.java
@@ -27,7 +27,9 @@
 /**
  * Subclass of {@link PlaybackFragment} that is responsible for providing a {@link SurfaceView}
  * and rendering video.
+ * @deprecated use {@link VideoSupportFragment}
  */
+@Deprecated
 public class VideoFragment extends PlaybackFragment {
     static final int SURFACE_NOT_CREATED = 0;
     static final int SURFACE_CREATED = 1;
diff --git a/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
index d123676..546e581 100644
--- a/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -24,7 +24,9 @@
 /**
  * {@link PlaybackGlueHost} implementation
  * the interaction between {@link PlaybackGlue} and {@link VideoFragment}.
+ * @deprecated use {@link VideoSupportFragmentGlueHost}
  */
+@Deprecated
 public class VideoFragmentGlueHost extends PlaybackFragmentGlueHost
         implements SurfaceHolderGlueHost {
     private final VideoFragment mFragment;
diff --git a/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 00bc073..2dcf51f 100644
--- a/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -225,6 +225,8 @@
         return true;
     }
 
+    ListUpdateCallback mListUpdateCallback;
+
     /**
      * Set a new item list to adapter. The DiffUtil will compute the difference and dispatch it to
      * specified position.
@@ -280,39 +282,43 @@
         mItems.addAll(itemList);
 
         // dispatch diff result
-        diffResult.dispatchUpdatesTo(new ListUpdateCallback() {
+        if (mListUpdateCallback == null) {
+            mListUpdateCallback = new ListUpdateCallback() {
 
-            @Override
-            public void onInserted(int position, int count) {
-                if (DEBUG) {
-                    Log.d(TAG, "onInserted");
+                @Override
+                public void onInserted(int position, int count) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onInserted");
+                    }
+                    notifyItemRangeInserted(position, count);
                 }
-                notifyItemRangeInserted(position, count);
-            }
 
-            @Override
-            public void onRemoved(int position, int count) {
-                if (DEBUG) {
-                    Log.d(TAG, "onRemoved");
+                @Override
+                public void onRemoved(int position, int count) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onRemoved");
+                    }
+                    notifyItemRangeRemoved(position, count);
                 }
-                notifyItemRangeRemoved(position, count);
-            }
 
-            @Override
-            public void onMoved(int fromPosition, int toPosition) {
-                if (DEBUG) {
-                    Log.d(TAG, "onMoved");
+                @Override
+                public void onMoved(int fromPosition, int toPosition) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onMoved");
+                    }
+                    notifyItemMoved(fromPosition, toPosition);
                 }
-                notifyItemMoved(fromPosition, toPosition);
-            }
 
-            @Override
-            public void onChanged(int position, int count, Object payload) {
-                if (DEBUG) {
-                    Log.d(TAG, "onChanged");
+                @Override
+                public void onChanged(int position, int count, Object payload) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onChanged");
+                    }
+                    notifyItemRangeChanged(position, count, payload);
                 }
-                notifyItemRangeChanged(position, count, payload);
-            }
-        });
+            };
+        }
+        diffResult.dispatchUpdatesTo(mListUpdateCallback);
+        mOldItems.clear();
     }
 }
diff --git a/android/support/v17/leanback/widget/BaseGridView.java b/android/support/v17/leanback/widget/BaseGridView.java
index f4e01c0..2ebec47 100644
--- a/android/support/v17/leanback/widget/BaseGridView.java
+++ b/android/support/v17/leanback/widget/BaseGridView.java
@@ -1134,7 +1134,7 @@
     @Override
     public void scrollToPosition(int position) {
         // dont abort the animateOut() animation, just record the position
-        if (mLayoutManager.mIsSlidingChildViews) {
+        if (mLayoutManager.isSlidingChildViews()) {
             mLayoutManager.setSelectionWithSub(position, 0, 0);
             return;
         }
@@ -1144,7 +1144,7 @@
     @Override
     public void smoothScrollToPosition(int position) {
         // dont abort the animateOut() animation, just record the position
-        if (mLayoutManager.mIsSlidingChildViews) {
+        if (mLayoutManager.isSlidingChildViews()) {
             mLayoutManager.setSelectionWithSub(position, 0, 0);
             return;
         }
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index dded071..d7020e9 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -217,9 +217,9 @@
                 mFocusPosition = getTargetPosition();
             }
             if (hasFocus()) {
-                mInSelection = true;
+                mFlag |= PF_IN_SELECTION;
                 targetView.requestFocus();
-                mInSelection = false;
+                mFlag &= ~PF_IN_SELECTION;
             }
             dispatchChildSelected();
             dispatchChildSelectedAndPositioned();
@@ -320,9 +320,9 @@
                 }
             }
             if (newSelected != null && hasFocus()) {
-                mInSelection = true;
+                mFlag |= PF_IN_SELECTION;
                 newSelected.requestFocus();
-                mInSelection = false;
+                mFlag &= ~PF_IN_SELECTION;
             }
         }
 
@@ -355,7 +355,8 @@
             if (mPendingMoves == 0) {
                 return null;
             }
-            int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0)
+            int direction = ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                    ? mPendingMoves > 0 : mPendingMoves < 0)
                     ? -1 : 1;
             if (mOrientation == HORIZONTAL) {
                 return new PointF(direction, 0);
@@ -386,10 +387,6 @@
     // effect smooth scrolling too over to bind an item view then drag the item view back.
     final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
 
-    // Represents whether child views are temporarily sliding out
-    boolean mIsSlidingChildViews;
-    boolean mLayoutEatenInSliding;
-
     String getTag() {
         return TAG + ":" + mBaseGridView.getId();
     }
@@ -444,15 +441,101 @@
 
     private static final Rect sTempRect = new Rect();
 
-    boolean mInLayout;
-    private boolean mInScroll;
-    boolean mInFastRelayout;
+    // 2 bits mask is for 3 STAGEs: 0, PF_STAGE_LAYOUT or PF_STAGE_SCROLL.
+    static final int PF_STAGE_MASK = 0x3;
+    static final int PF_STAGE_LAYOUT = 0x1;
+    static final int PF_STAGE_SCROLL = 0x2;
+
+    // Flag for "in fast relayout", determined by layoutInit() result.
+    static final int PF_FAST_RELAYOUT = 1 << 2;
+
+    // Flag for the selected item being updated in fast relayout.
+    static final int PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION = 1 << 3;
     /**
      * During full layout pass, when GridView had focus: onLayoutChildren will
      * skip non-focusable child and adjust mFocusPosition.
      */
-    boolean mInLayoutSearchFocus;
-    boolean mInSelection = false;
+    static final int PF_IN_LAYOUT_SEARCH_FOCUS = 1 << 4;
+
+    // flag to prevent reentry if it's already processing selection request.
+    static final int PF_IN_SELECTION = 1 << 5;
+
+    // Represents whether child views are temporarily sliding out
+    static final int PF_SLIDING = 1 << 6;
+    static final int PF_LAYOUT_EATEN_IN_SLIDING = 1 << 7;
+
+    /**
+     * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
+     */
+    static final int PF_FORCE_FULL_LAYOUT = 1 << 8;
+
+    /**
+     * True if layout is enabled.
+     */
+    static final int PF_LAYOUT_ENABLED = 1 << 9;
+
+    /**
+     * Flag controlling whether the current/next layout should
+     * be updating the secondary size of rows.
+     */
+    static final int PF_ROW_SECONDARY_SIZE_REFRESH = 1 << 10;
+
+    /**
+     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
+     *  default is false.
+     */
+    static final int PF_FOCUS_OUT_FRONT = 1 << 11;
+
+    /**
+     * Allow DPAD key to navigate out at the end of the view, default is false.
+     */
+    static final int PF_FOCUS_OUT_END = 1 << 12;
+
+    static final int PF_FOCUS_OUT_MASKS = PF_FOCUS_OUT_FRONT | PF_FOCUS_OUT_END;
+
+    /**
+     *  Allow DPAD key to navigate out of second axis.
+     *  default is true.
+     */
+    static final int PF_FOCUS_OUT_SIDE_START = 1 << 13;
+
+    /**
+     * Allow DPAD key to navigate out of second axis.
+     */
+    static final int PF_FOCUS_OUT_SIDE_END = 1 << 14;
+
+    static final int PF_FOCUS_OUT_SIDE_MASKS = PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END;
+
+    /**
+     * True if focus search is disabled.
+     */
+    static final int PF_FOCUS_SEARCH_DISABLED = 1 << 15;
+
+    /**
+     * True if prune child,  might be disabled during transition.
+     */
+    static final int PF_PRUNE_CHILD = 1 << 16;
+
+    /**
+     * True if scroll content,  might be disabled during transition.
+     */
+    static final int PF_SCROLL_ENABLED = 1 << 17;
+
+    /**
+     * Set to true for RTL layout in horizontal orientation
+     */
+    static final int PF_REVERSE_FLOW_PRIMARY = 1 << 18;
+
+    /**
+     * Set to true for RTL layout in vertical orientation
+     */
+    static final int PF_REVERSE_FLOW_SECONDARY = 1 << 19;
+
+    static final int PF_REVERSE_FLOW_MASK = PF_REVERSE_FLOW_PRIMARY | PF_REVERSE_FLOW_SECONDARY;
+
+    int mFlag = PF_LAYOUT_ENABLED
+            | PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END
+            | PF_PRUNE_CHILD | PF_SCROLL_ENABLED;
 
     private OnChildSelectedListener mChildSelectedListener = null;
 
@@ -493,16 +576,6 @@
     private int mPrimaryScrollExtra;
 
     /**
-     * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
-     */
-    private boolean mForceFullLayout;
-
-    /**
-     * True if layout is enabled.
-     */
-    private boolean mLayoutEnabled = true;
-
-    /**
      * override child visibility
      */
     @Visibility
@@ -535,12 +608,6 @@
     private int[] mRowSizeSecondary;
 
     /**
-     * Flag controlling whether the current/next layout should
-     * be updating the secondary size of rows.
-     */
-    private boolean mRowSecondarySizeRefresh;
-
-    /**
      * The maximum measured size of the view.
      */
     private int mMaxSizeSecondary;
@@ -605,58 +672,11 @@
     private int mExtraLayoutSpace;
 
     /**
-     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
-     *  default is false.
-     */
-    private boolean mFocusOutFront;
-
-    /**
-     * Allow DPAD key to navigate out at the end of the view, default is false.
-     */
-    private boolean mFocusOutEnd;
-
-    /**
-     *  Allow DPAD key to navigate out of second axis.
-     *  default is true.
-     */
-    private boolean mFocusOutSideStart = true;
-
-    /**
-     * Allow DPAD key to navigate out of second axis.
-     */
-    private boolean mFocusOutSideEnd = true;
-
-    /**
-     * True if focus search is disabled.
-     */
-    private boolean mFocusSearchDisabled;
-
-    /**
-     * True if prune child,  might be disabled during transition.
-     */
-    private boolean mPruneChild = true;
-
-    /**
-     * True if scroll content,  might be disabled during transition.
-     */
-    private boolean mScrollEnabled = true;
-
-    /**
      * Temporary variable: an int array of length=2.
      */
     static int[] sTwoInts = new int[2];
 
     /**
-     * Set to true for RTL layout in horizontal orientation
-     */
-    boolean mReverseFlowPrimary = false;
-
-    /**
-     * Set to true for RTL layout in vertical orientation
-     */
-    private boolean mReverseFlowSecondary = false;
-
-    /**
      * Temporaries used for measuring.
      */
     private int[] mMeasuredDimension = new int[2];
@@ -685,24 +705,21 @@
         mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
         mWindowAlignment.setOrientation(orientation);
         mItemAlignment.setOrientation(orientation);
-        mForceFullLayout = true;
+        mFlag |= PF_FORCE_FULL_LAYOUT;
     }
 
     public void onRtlPropertiesChanged(int layoutDirection) {
-        boolean reversePrimary, reverseSecondary;
+        final int flags;
         if (mOrientation == HORIZONTAL) {
-            reversePrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
-            reverseSecondary = false;
+            flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_PRIMARY : 0;
         } else {
-            reverseSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
-            reversePrimary = false;
+            flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_SECONDARY : 0;
         }
-        if (mReverseFlowPrimary == reversePrimary && mReverseFlowSecondary == reverseSecondary) {
+        if ((mFlag & PF_REVERSE_FLOW_MASK) == flags) {
             return;
         }
-        mReverseFlowPrimary = reversePrimary;
-        mReverseFlowSecondary = reverseSecondary;
-        mForceFullLayout = true;
+        mFlag = (mFlag & ~PF_REVERSE_FLOW_MASK) | flags;
+        mFlag |= PF_FORCE_FULL_LAYOUT;
         mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
     }
 
@@ -775,13 +792,15 @@
     }
 
     public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
-        mFocusOutFront = throughFront;
-        mFocusOutEnd = throughEnd;
+        mFlag = (mFlag & ~PF_FOCUS_OUT_MASKS)
+                | (throughFront ? PF_FOCUS_OUT_FRONT : 0)
+                | (throughEnd ? PF_FOCUS_OUT_END : 0);
     }
 
     public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
-        mFocusOutSideStart = throughStart;
-        mFocusOutSideEnd = throughEnd;
+        mFlag = (mFlag & ~PF_FOCUS_OUT_SIDE_MASKS)
+                | (throughStart ? PF_FOCUS_OUT_SIDE_START : 0)
+                | (throughEnd ? PF_FOCUS_OUT_SIDE_END : 0);
     }
 
     public void setNumRows(int numRows) {
@@ -971,7 +990,7 @@
         // layout warning.
         // If not in layout, we may be scrolling in which case the child layout request will be
         // eaten by recyclerview.  Post a requestLayout.
-        if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
+        if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && !mBaseGridView.isLayoutRequested()) {
             int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
                 if (getChildAt(i).isLayoutRequested()) {
@@ -1177,19 +1196,19 @@
             mSubFocusPosition = 0;
         }
         if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
-                && !mForceFullLayout && mGrid.getNumRows() == mNumRows) {
+                && (mFlag & PF_FORCE_FULL_LAYOUT) == 0 && mGrid.getNumRows() == mNumRows) {
             updateScrollController();
             updateSecondaryScrollLimits();
             mGrid.setSpacing(mSpacingPrimary);
             return true;
         } else {
-            mForceFullLayout = false;
+            mFlag &= ~PF_FORCE_FULL_LAYOUT;
 
             if (mGrid == null || mNumRows != mGrid.getNumRows()
-                    || mReverseFlowPrimary != mGrid.isReversedFlow()) {
+                    || ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) != mGrid.isReversedFlow()) {
                 mGrid = Grid.createGrid(mNumRows);
                 mGrid.setProvider(mGridProvider);
-                mGrid.setReversedFlow(mReverseFlowPrimary);
+                mGrid.setReversedFlow((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0);
             }
             initScrollController();
             updateSecondaryScrollLimits();
@@ -1216,7 +1235,7 @@
         int start = 0;
         // Iterate from left to right, which is a different index traversal
         // in RTL flow
-        if (mReverseFlowSecondary) {
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
             for (int i = mNumRows-1; i > rowIndex; i--) {
                 start += getRowSizeSecondary(i) + mSpacingSecondary;
             }
@@ -1229,7 +1248,7 @@
     }
 
     private int getSizeSecondary() {
-        int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
+        int rightmostIndex = (mFlag & PF_REVERSE_FLOW_SECONDARY) != 0 ? 0 : mNumRows - 1;
         return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
     }
 
@@ -1366,8 +1385,9 @@
      * Checks if we need to update row secondary sizes.
      */
     private void updateRowSecondarySizeRefresh() {
-        mRowSecondarySizeRefresh = processRowSizeSecondary(false);
-        if (mRowSecondarySizeRefresh) {
+        mFlag = (mFlag & ~PF_ROW_SECONDARY_SIZE_REFRESH)
+                | (processRowSizeSecondary(false) ? PF_ROW_SECONDARY_SIZE_REFRESH : 0);
+        if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
             if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
             forceRequestLayout();
         }
@@ -1599,7 +1619,7 @@
                     mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
                 }
                 int subindex = getSubPositionByView(v, v.findFocus());
-                if (!mInLayout) {
+                if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
                     // when we are appending item during scroll pass and the item's position
                     // matches the mFocusPosition,  we should signal a childSelected event.
                     // However if we are still running PendingMoveSmoothScroller,  we defer and
@@ -1610,20 +1630,20 @@
                             && mPendingMoveSmoothScroller == null) {
                         dispatchChildSelected();
                     }
-                } else if (!mInFastRelayout) {
+                } else if ((mFlag & PF_FAST_RELAYOUT) == 0) {
                     // fastRelayout will dispatch event at end of onLayoutChildren().
                     // For full layout, two situations here:
                     // 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition.
                     // 2. mInLayoutSearchFocus is true:  dispatchChildSelected() on first child
                     //    equal to or after mFocusPosition that can take focus.
-                    if (!mInLayoutSearchFocus && index == mFocusPosition
+                    if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) == 0 && index == mFocusPosition
                             && subindex == mSubFocusPosition) {
                         dispatchChildSelected();
-                    } else if (mInLayoutSearchFocus && index >= mFocusPosition
+                    } else if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) != 0 && index >= mFocusPosition
                             && v.hasFocusable()) {
                         mFocusPosition = index;
                         mSubFocusPosition = subindex;
-                        mInLayoutSearchFocus = false;
+                        mFlag &= ~PF_IN_LAYOUT_SEARCH_FOCUS;
                         dispatchChildSelected();
                     }
                 }
@@ -1663,7 +1683,7 @@
             if (!mState.isPreLayout()) {
                 updateScrollLimits();
             }
-            if (!mInLayout && mPendingMoveSmoothScroller != null) {
+            if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && mPendingMoveSmoothScroller != null) {
                 mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
             }
             if (mChildLaidOutListener != null) {
@@ -1677,7 +1697,7 @@
         public void removeItem(int index) {
             if (TRACE) TraceCompat.beginSection("removeItem");
             View v = findViewByPosition(index - mPositionDeltaInPreLayout);
-            if (mInLayout) {
+            if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
                 detachAndScrapView(v, mRecycler);
             } else {
                 removeAndRecycleView(v, mRecycler);
@@ -1688,7 +1708,7 @@
         @Override
         public int getEdge(int index) {
             View v = findViewByPosition(index - mPositionDeltaInPreLayout);
-            return mReverseFlowPrimary ? getViewMax(v) : getViewMin(v);
+            return (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? getViewMax(v) : getViewMin(v);
         }
 
         @Override
@@ -1705,7 +1725,7 @@
             sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
         }
         final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
-        final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary)
+        final int horizontalGravity = (mFlag & PF_REVERSE_FLOW_MASK) != 0
                 ? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
                 View.LAYOUT_DIRECTION_RTL)
                 : mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
@@ -1781,16 +1801,16 @@
     }
 
     private void removeInvisibleViewsAtEnd() {
-        if (mPruneChild && !mIsSlidingChildViews) {
-            mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
-                    mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
+        if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+            mGrid.removeInvisibleItemsAtEnd(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                    ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
         }
     }
 
     private void removeInvisibleViewsAtFront() {
-        if (mPruneChild && !mIsSlidingChildViews) {
-            mGrid.removeInvisibleItemsAtFront(mFocusPosition,
-                    mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace);
+        if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+            mGrid.removeInvisibleItemsAtFront(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                    ? mSizePrimary + mExtraLayoutSpace : -mExtraLayoutSpace);
         }
     }
 
@@ -1799,16 +1819,16 @@
     }
 
     void slideIn() {
-        if (mIsSlidingChildViews) {
-            mIsSlidingChildViews = false;
+        if ((mFlag & PF_SLIDING) != 0) {
+            mFlag &= ~PF_SLIDING;
             if (mFocusPosition >= 0) {
                 scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
             } else {
-                mLayoutEatenInSliding = false;
+                mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
                 requestLayout();
             }
-            if (mLayoutEatenInSliding) {
-                mLayoutEatenInSliding = false;
+            if ((mFlag & PF_LAYOUT_EATEN_IN_SLIDING) != 0) {
+                mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
                 if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
                     mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                         @Override
@@ -1838,7 +1858,7 @@
                 }
             }
         } else {
-            if (mReverseFlowPrimary) {
+            if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
                 distance = getWidth();
                 if (getChildCount() > 0) {
                     int start = getChildAt(0).getRight();
@@ -1861,14 +1881,18 @@
         return distance;
     }
 
+    boolean isSlidingChildViews() {
+        return (mFlag & PF_SLIDING) != 0;
+    }
+
     /**
      * Temporarily slide out child and block layout and scroll requests.
      */
     void slideOut() {
-        if (mIsSlidingChildViews) {
+        if ((mFlag & PF_SLIDING) != 0) {
             return;
         }
-        mIsSlidingChildViews = true;
+        mFlag |= PF_SLIDING;
         if (getChildCount() == 0) {
             return;
         }
@@ -1886,13 +1910,13 @@
     }
 
     private void appendVisibleItems() {
-        mGrid.appendVisibleItems(mReverseFlowPrimary
+        mGrid.appendVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
                 ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
                 : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
     }
 
     private void prependVisibleItems() {
-        mGrid.prependVisibleItems(mReverseFlowPrimary
+        mGrid.prependVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
                 ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
                 : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
     }
@@ -1907,6 +1931,7 @@
         final int childCount = getChildCount();
         int position = mGrid.getFirstVisibleIndex();
         int index = 0;
+        mFlag &= ~PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
         for (; index < childCount; index++, position++) {
             View view = getChildAt(index);
             // We don't hit fastRelayout() if State.didStructure() is true, but prelayout may add
@@ -1932,6 +1957,7 @@
 
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
             if (lp.viewNeedsUpdate()) {
+                mFlag |= PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
                 detachAndScrapView(view, mRecycler);
                 view = getViewForPosition(position);
                 addView(view, index);
@@ -1960,7 +1986,7 @@
                 detachAndScrapView(v, mRecycler);
             }
             mGrid.invalidateItemsAfter(position);
-            if (mPruneChild) {
+            if ((mFlag & PF_PRUNE_CHILD) != 0) {
                 // in regular prune child mode, we just append items up to edge limit
                 appendVisibleItems();
                 if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) {
@@ -2108,7 +2134,7 @@
             Log.v(getTag(), "layoutChildren start numRows " + mNumRows
                     + " inPreLayout " + state.isPreLayout()
                     + " didStructureChange " + state.didStructureChange()
-                    + " mForceFullLayout " + mForceFullLayout);
+                    + " mForceFullLayout " + ((mFlag & PF_FORCE_FULL_LAYOUT) != 0));
             Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
         }
 
@@ -2121,20 +2147,20 @@
             return;
         }
 
-        if (mIsSlidingChildViews) {
+        if ((mFlag & PF_SLIDING) != 0) {
             // if there is already children, delay the layout process until slideIn(), if it's
             // first time layout children: scroll them offscreen at end of onLayoutChildren()
             if (getChildCount() > 0) {
-                mLayoutEatenInSliding = true;
+                mFlag |= PF_LAYOUT_EATEN_IN_SLIDING;
                 return;
             }
         }
-        if (!mLayoutEnabled) {
+        if ((mFlag & PF_LAYOUT_ENABLED) == 0) {
             discardLayoutInfo();
             removeAndRecycleAllViews(recycler);
             return;
         }
-        mInLayout = true;
+        mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_LAYOUT;
 
         saveContext(recycler, state);
         if (state.isPreLayout()) {
@@ -2172,7 +2198,7 @@
                 appendVisibleItems();
                 prependVisibleItems();
             }
-            mInLayout = false;
+            mFlag &= ~PF_STAGE_MASK;
             leaveContext();
             if (DEBUG) Log.v(getTag(), "layoutChildren end");
             return;
@@ -2206,13 +2232,16 @@
             deltaSecondary = state.getRemainingScrollHorizontal();
             deltaPrimary = state.getRemainingScrollVertical();
         }
-        if (mInFastRelayout = layoutInit()) {
+        if (layoutInit()) {
+            mFlag |= PF_FAST_RELAYOUT;
             // If grid view is empty, we will start from mFocusPosition
             mGrid.setStart(mFocusPosition);
             fastRelayout();
         } else {
+            mFlag &= ~PF_FAST_RELAYOUT;
             // layoutInit() has detached all views, so start from scratch
-            mInLayoutSearchFocus = hadFocus;
+            mFlag = (mFlag & ~PF_IN_LAYOUT_SEARCH_FOCUS)
+                    | (hadFocus ? PF_IN_LAYOUT_SEARCH_FOCUS : 0);
             int startFromPosition, endPos;
             if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
                     || mFocusPosition < firstVisibleIndex)) {
@@ -2270,27 +2299,30 @@
             Log.d(getTag(), sw.toString());
         }
 
-        if (mRowSecondarySizeRefresh) {
-            mRowSecondarySizeRefresh = false;
+        if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
+            mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
         } else {
             updateRowSecondarySizeRefresh();
         }
 
-        // For fastRelayout, only dispatch event when focus position changes.
-        if (mInFastRelayout && (mFocusPosition != savedFocusPos || mSubFocusPosition
-                != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) {
+        // For fastRelayout, only dispatch event when focus position changes or selected item
+        // being updated.
+        if ((mFlag & PF_FAST_RELAYOUT) != 0 && (mFocusPosition != savedFocusPos || mSubFocusPosition
+                != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView
+                || (mFlag & PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION) != 0)) {
             dispatchChildSelected();
-        } else if (!mInFastRelayout && mInLayoutSearchFocus) {
+        } else if ((mFlag & (PF_FAST_RELAYOUT | PF_IN_LAYOUT_SEARCH_FOCUS))
+                == PF_IN_LAYOUT_SEARCH_FOCUS) {
             // For full layout we dispatchChildSelected() in createItem() unless searched all
             // children and found none is focusable then dispatchChildSelected() here.
             dispatchChildSelected();
         }
         dispatchChildSelectedAndPositioned();
-        if (mIsSlidingChildViews) {
+        if ((mFlag & PF_SLIDING) != 0) {
             scrollDirectionPrimary(getSlideOutDistance());
         }
 
-        mInLayout = false;
+        mFlag &= ~PF_STAGE_MASK;
         leaveContext();
         if (DEBUG) Log.v(getTag(), "layoutChildren end");
     }
@@ -2324,11 +2356,11 @@
     @Override
     public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
         if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
-        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+        if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
             return 0;
         }
         saveContext(recycler, state);
-        mInScroll = true;
+        mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
         int result;
         if (mOrientation == HORIZONTAL) {
             result = scrollDirectionPrimary(dx);
@@ -2336,17 +2368,17 @@
             result = scrollDirectionSecondary(dx);
         }
         leaveContext();
-        mInScroll = false;
+        mFlag &= ~PF_STAGE_MASK;
         return result;
     }
 
     @Override
     public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
         if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
-        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+        if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
             return 0;
         }
-        mInScroll = true;
+        mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
         saveContext(recycler, state);
         int result;
         if (mOrientation == VERTICAL) {
@@ -2355,7 +2387,7 @@
             result = scrollDirectionSecondary(dy);
         }
         leaveContext();
-        mInScroll = false;
+        mFlag &= ~PF_STAGE_MASK;
         return result;
     }
 
@@ -2367,7 +2399,7 @@
         // 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 ((mFlag & PF_SLIDING) == 0 && (mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
             if (da > 0) {
                 if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
                     int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
@@ -2389,7 +2421,7 @@
             return 0;
         }
         offsetChildrenPrimary(-da);
-        if (mInLayout) {
+        if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
             updateScrollLimits();
             if (TRACE) TraceCompat.endSection();
             return da;
@@ -2398,7 +2430,7 @@
         int childCount = getChildCount();
         boolean updated;
 
-        if (mReverseFlowPrimary ? da > 0 : da < 0) {
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
             prependVisibleItems();
         } else {
             appendVisibleItems();
@@ -2407,7 +2439,7 @@
         childCount = getChildCount();
 
         if (TRACE) TraceCompat.beginSection("remove");
-        if (mReverseFlowPrimary ? da > 0 : da < 0) {
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
             removeInvisibleViewsAtEnd();
         } else {
             removeInvisibleViewsAtFront();
@@ -2476,7 +2508,7 @@
         }
         int highVisiblePos, lowVisiblePos;
         int highMaxPos, lowMinPos;
-        if (!mReverseFlowPrimary) {
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) == 0) {
             highVisiblePos = mGrid.getLastVisibleIndex();
             highMaxPos = mState.getItemCount() - 1;
             lowVisiblePos = mGrid.getFirstVisibleIndex();
@@ -2614,14 +2646,14 @@
         // scrollToView() is based on Adapter position. Only call scrollToView() when item
         // is still valid.
         if (view != null && getAdapterPositionByView(view) == position) {
-            mInSelection = true;
+            mFlag |= PF_IN_SELECTION;
             scrollToView(view, smooth);
-            mInSelection = false;
+            mFlag &= ~PF_IN_SELECTION;
         } else {
             mFocusPosition = position;
             mSubFocusPosition = subposition;
             mFocusPositionOffset = Integer.MIN_VALUE;
-            if (!mLayoutEnabled || mIsSlidingChildViews) {
+            if ((mFlag & PF_LAYOUT_ENABLED) == 0 || (mFlag & PF_SLIDING) != 0) {
                 return;
             }
             if (smooth) {
@@ -2637,7 +2669,7 @@
                     mSubFocusPosition = 0;
                 }
             } else {
-                mForceFullLayout = true;
+                mFlag |= PF_FORCE_FULL_LAYOUT;
                 requestLayout();
             }
         }
@@ -2654,7 +2686,8 @@
                 final int firstChildPos = getPosition(getChildAt(0));
                 // TODO We should be able to deduce direction from bounds of current and target
                 // focus, rather than making assumptions about positions and directionality
-                final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos
+                final boolean isStart = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                        ? targetPosition > firstChildPos
                         : targetPosition < firstChildPos;
                 final int direction = isStart ? -1 : 1;
                 if (mOrientation == HORIZONTAL) {
@@ -2788,14 +2821,14 @@
 
     @Override
     public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
-        if (mFocusSearchDisabled) {
+        if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
             return true;
         }
         if (getAdapterPositionByView(child) == NO_POSITION) {
             // This is could be the last view in DISAPPEARING animation.
             return true;
         }
-        if (!mInLayout && !mInSelection && !mInScroll) {
+        if ((mFlag & (PF_STAGE_MASK | PF_IN_SELECTION)) == 0) {
             scrollToView(child, focused, true);
         }
         return true;
@@ -2865,7 +2898,7 @@
      */
     private void scrollToView(View view, View childView, boolean smooth, int extraDelta,
             int extraDeltaSecondary) {
-        if (mIsSlidingChildViews) {
+        if ((mFlag & PF_SLIDING) != 0) {
             return;
         }
         int newFocusPosition = getAdapterPositionByView(view);
@@ -2874,7 +2907,7 @@
             mFocusPosition = newFocusPosition;
             mSubFocusPosition = newSubFocusPosition;
             mFocusPositionOffset = 0;
-            if (!mInLayout) {
+            if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
                 dispatchChildSelected();
             }
             if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
@@ -2889,7 +2922,7 @@
             // by setSelection())
             view.requestFocus();
         }
-        if (!mScrollEnabled && smooth) {
+        if ((mFlag & PF_SCROLL_ENABLED) == 0 && smooth) {
             return;
         }
         if (getScrollPosition(view, childView, sTwoInts)
@@ -3007,7 +3040,7 @@
     }
 
     private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
-        if (mInLayout) {
+        if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
             scrollDirectionPrimary(scrollPrimary);
             scrollDirectionSecondary(scrollSecondary);
         } else {
@@ -3030,22 +3063,23 @@
     }
 
     public void setPruneChild(boolean pruneChild) {
-        if (mPruneChild != pruneChild) {
-            mPruneChild = pruneChild;
-            if (mPruneChild) {
+        if (((mFlag & PF_PRUNE_CHILD) != 0) != pruneChild) {
+            mFlag = (mFlag & ~PF_PRUNE_CHILD) | (pruneChild ? PF_PRUNE_CHILD : 0);
+            if (pruneChild) {
                 requestLayout();
             }
         }
     }
 
     public boolean getPruneChild() {
-        return mPruneChild;
+        return (mFlag & PF_PRUNE_CHILD) != 0;
     }
 
     public void setScrollEnabled(boolean scrollEnabled) {
-        if (mScrollEnabled != scrollEnabled) {
-            mScrollEnabled = scrollEnabled;
-            if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
+        if (((mFlag & PF_SCROLL_ENABLED) != 0) != scrollEnabled) {
+            mFlag = (mFlag & ~PF_SCROLL_ENABLED) | (scrollEnabled ? PF_SCROLL_ENABLED : 0);
+            if (((mFlag & PF_SCROLL_ENABLED) != 0)
+                    && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
                     && mFocusPosition != NO_POSITION) {
                 scrollToSelection(mFocusPosition, mSubFocusPosition,
                         true, mPrimaryScrollExtra);
@@ -3054,7 +3088,7 @@
     }
 
     public boolean isScrollEnabled() {
-        return mScrollEnabled;
+        return (mFlag & PF_SCROLL_ENABLED) != 0;
     }
 
     private int findImmediateChildIndex(View view) {
@@ -3088,16 +3122,16 @@
     }
 
     void setFocusSearchDisabled(boolean disabled) {
-        mFocusSearchDisabled = disabled;
+        mFlag = (mFlag & ~PF_FOCUS_SEARCH_DISABLED) | (disabled ? PF_FOCUS_SEARCH_DISABLED : 0);
     }
 
     boolean isFocusSearchDisabled() {
-        return mFocusSearchDisabled;
+        return (mFlag & PF_FOCUS_SEARCH_DISABLED) != 0;
     }
 
     @Override
     public View onInterceptFocusSearch(View focused, int direction) {
-        if (mFocusSearchDisabled) {
+        if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
             return focused;
         }
 
@@ -3132,27 +3166,27 @@
         int movement = getMovement(direction);
         final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
         if (movement == NEXT_ITEM) {
-            if (isScroll || !mFocusOutEnd) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_END) == 0) {
                 result = focused;
             }
-            if (mScrollEnabled && !hasCreatedLastItem()) {
+            if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedLastItem()) {
                 processPendingMovement(true);
                 result = focused;
             }
         } else if (movement == PREV_ITEM) {
-            if (isScroll || !mFocusOutFront) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_FRONT) == 0) {
                 result = focused;
             }
-            if (mScrollEnabled && !hasCreatedFirstItem()) {
+            if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedFirstItem()) {
                 processPendingMovement(false);
                 result = focused;
             }
         } else if (movement == NEXT_ROW) {
-            if (isScroll || !mFocusOutSideEnd) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_END) == 0) {
                 result = focused;
             }
         } else if (movement == PREV_ROW) {
-            if (isScroll || !mFocusOutSideStart) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_START) == 0) {
                 result = focused;
             }
         }
@@ -3191,7 +3225,7 @@
     @Override
     public boolean onAddFocusables(RecyclerView recyclerView,
             ArrayList<View> views, int direction, int focusableMode) {
-        if (mFocusSearchDisabled) {
+        if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
             return true;
         }
         // If this viewgroup or one of its children currently has focus then we
@@ -3423,10 +3457,10 @@
         if (mOrientation == HORIZONTAL) {
             switch(direction) {
                 case View.FOCUS_LEFT:
-                    movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
+                    movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? PREV_ITEM : NEXT_ITEM;
                     break;
                 case View.FOCUS_RIGHT:
-                    movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
+                    movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? NEXT_ITEM : PREV_ITEM;
                     break;
                 case View.FOCUS_UP:
                     movement = PREV_ROW;
@@ -3438,10 +3472,10 @@
         } else if (mOrientation == VERTICAL) {
             switch(direction) {
                 case View.FOCUS_LEFT:
-                    movement = (!mReverseFlowSecondary) ? PREV_ROW : NEXT_ROW;
+                    movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? PREV_ROW : NEXT_ROW;
                     break;
                 case View.FOCUS_RIGHT:
-                    movement = (!mReverseFlowSecondary) ? NEXT_ROW : PREV_ROW;
+                    movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? NEXT_ROW : PREV_ROW;
                     break;
                 case View.FOCUS_UP:
                     movement = PREV_ITEM;
@@ -3497,12 +3531,12 @@
     private void discardLayoutInfo() {
         mGrid = null;
         mRowSizeSecondary = null;
-        mRowSecondarySizeRefresh = false;
+        mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
     }
 
     public void setLayoutEnabled(boolean layoutEnabled) {
-        if (mLayoutEnabled != layoutEnabled) {
-            mLayoutEnabled = layoutEnabled;
+        if (((mFlag & PF_LAYOUT_ENABLED) != 0) != layoutEnabled) {
+            mFlag = (mFlag & ~PF_LAYOUT_ENABLED) | (layoutEnabled ? PF_LAYOUT_ENABLED : 0);
             requestLayout();
         }
     }
@@ -3592,7 +3626,7 @@
         mFocusPosition = loadingState.index;
         mFocusPositionOffset = 0;
         mChildrenStates.loadFromBundle(loadingState.childStates);
-        mForceFullLayout = true;
+        mFlag |= PF_FORCE_FULL_LAYOUT;
         requestLayout();
         if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
     }
@@ -3699,9 +3733,9 @@
         if (newSelected != null) {
             if (preventScroll) {
                 if (hasFocus()) {
-                    mInSelection = true;
+                    mFlag |= PF_IN_SELECTION;
                     newSelected.requestFocus();
-                    mInSelection = false;
+                    mFlag &= ~PF_IN_SELECTION;
                 }
                 mFocusPosition = focusPosition;
                 mSubFocusPosition = 0;
@@ -3717,11 +3751,11 @@
             AccessibilityNodeInfoCompat info) {
         saveContext(recycler, state);
         int count = state.getItemCount();
-        if (mScrollEnabled && count > 1 && !isItemFullyVisible(0)) {
+        if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(0)) {
             info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
             info.setScrollable(true);
         }
-        if (mScrollEnabled && count > 1 && !isItemFullyVisible(count - 1)) {
+        if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(count - 1)) {
             info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
             info.setScrollable(true);
         }
diff --git a/android/support/v17/leanback/widget/GuidedActionAdapter.java b/android/support/v17/leanback/widget/GuidedActionAdapter.java
index 5b755f5..51b29e2 100644
--- a/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -15,7 +15,9 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.v7.util.DiffUtil;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.util.Log;
@@ -103,6 +105,7 @@
     private ClickListener mClickListener;
     final GuidedActionsStylist mStylist;
     GuidedActionAdapterGroup mGroup;
+    DiffCallback<GuidedAction> mDiffCallback;
 
     private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
         @Override
@@ -145,20 +148,78 @@
         mActionOnFocusListener = new ActionOnFocusListener(focusListener);
         mActionEditListener = new ActionEditListener();
         mIsSubAdapter = isSubAdapter;
+        if (!isSubAdapter) {
+            mDiffCallback = GuidedActionDiffCallback.getInstance();
+        }
     }
 
     /**
-     * Sets the list of actions managed by this adapter.
+     * Change DiffCallback used in {@link #setActions(List)}. Set to null for firing a
+     * general {@link #notifyDataSetChanged()}.
+     *
+     * @param diffCallback
+     */
+    public void setDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+        mDiffCallback = diffCallback;
+    }
+
+    /**
+     * Sets the list of actions managed by this adapter. Use {@link #setDiffCallback(DiffCallback)}
+     * to change DiffCallback.
      * @param actions The list of actions to be managed.
      */
-    public void setActions(List<GuidedAction> actions) {
+    public void setActions(final List<GuidedAction> actions) {
         if (!mIsSubAdapter) {
             mStylist.collapseAction(false);
         }
         mActionOnFocusListener.unFocus();
-        mActions.clear();
-        mActions.addAll(actions);
-        notifyDataSetChanged();
+        if (mDiffCallback != null) {
+            // temporary variable used for DiffCallback
+            final List<GuidedAction> oldActions = new ArrayList();
+            oldActions.addAll(mActions);
+
+            // update items.
+            mActions.clear();
+            mActions.addAll(actions);
+
+            DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+                @Override
+                public int getOldListSize() {
+                    return oldActions.size();
+                }
+
+                @Override
+                public int getNewListSize() {
+                    return mActions.size();
+                }
+
+                @Override
+                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+                    return mDiffCallback.areItemsTheSame(oldActions.get(oldItemPosition),
+                            mActions.get(newItemPosition));
+                }
+
+                @Override
+                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+                    return mDiffCallback.areContentsTheSame(oldActions.get(oldItemPosition),
+                            mActions.get(newItemPosition));
+                }
+
+                @Nullable
+                @Override
+                public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+                    return mDiffCallback.getChangePayload(oldActions.get(oldItemPosition),
+                            mActions.get(newItemPosition));
+                }
+            });
+
+            // dispatch diff result
+            diffResult.dispatchUpdatesTo(this);
+        } else {
+            mActions.clear();
+            mActions.addAll(actions);
+            notifyDataSetChanged();
+        }
     }
 
     /**
diff --git a/android/support/v17/leanback/widget/GuidedActionDiffCallback.java b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
new file mode 100644
index 0000000..d4d4d77
--- /dev/null
+++ b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+/**
+ * DiffCallback used for GuidedActions, see {@link
+ * android.support.v17.leanback.app.GuidedStepSupportFragment#setActionsDiffCallback(DiffCallback)}.
+ */
+public class GuidedActionDiffCallback extends DiffCallback<GuidedAction> {
+
+    static final GuidedActionDiffCallback sInstance = new GuidedActionDiffCallback();
+
+    /**
+     * Returns the singleton GuidedActionDiffCallback.
+     * @return The singleton GuidedActionDiffCallback.
+     */
+    public static final GuidedActionDiffCallback getInstance() {
+        return sInstance;
+    }
+
+    @Override
+    public boolean areItemsTheSame(@NonNull GuidedAction oldItem, @NonNull GuidedAction newItem) {
+        if (oldItem == null) {
+            return newItem == null;
+        } else if (newItem == null) {
+            return false;
+        }
+        return oldItem.getId() == newItem.getId();
+    }
+
+    @Override
+    public boolean areContentsTheSame(@NonNull GuidedAction oldItem,
+            @NonNull GuidedAction newItem) {
+        if (oldItem == null) {
+            return newItem == null;
+        } else if (newItem == null) {
+            return false;
+        }
+        return oldItem.getCheckSetId() == newItem.getCheckSetId()
+                && oldItem.mActionFlags == newItem.mActionFlags
+                && TextUtils.equals(oldItem.getTitle(), newItem.getTitle())
+                && TextUtils.equals(oldItem.getDescription(), newItem.getDescription())
+                && oldItem.getInputType() == newItem.getInputType()
+                && TextUtils.equals(oldItem.getEditTitle(), newItem.getEditTitle())
+                && TextUtils.equals(oldItem.getEditDescription(), newItem.getEditDescription())
+                && oldItem.getEditInputType() == newItem.getEditInputType()
+                && oldItem.getDescriptionEditInputType() == newItem.getDescriptionEditInputType();
+    }
+}
diff --git a/android/support/v17/leanback/widget/ObjectAdapter.java b/android/support/v17/leanback/widget/ObjectAdapter.java
index 535f81b..d411f9e 100644
--- a/android/support/v17/leanback/widget/ObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ObjectAdapter.java
@@ -13,7 +13,10 @@
  */
 package android.support.v17.leanback.widget;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
 import android.database.Observable;
+import android.support.annotation.RestrictTo;
 
 /**
  * Base class adapter to be used in leanback activities.  Provides access to a data model and is
@@ -132,6 +135,10 @@
                 mObservers.get(i).onItemMoved(positionStart, toPosition);
             }
         }
+
+        boolean hasObserver() {
+            return mObservers.size() > 0;
+        }
     }
 
     private final DataObservable mObservable = new DataObservable();
@@ -207,6 +214,14 @@
     }
 
     /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public final boolean hasObserver() {
+        return mObservable.hasObserver();
+    }
+
+    /**
      * Unregisters all DataObservers for this ObjectAdapter.
      */
     public final void unregisterAllObservers() {
diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java
index 614ff35..78161a8 100644
--- a/android/support/v4/app/FragmentActivity.java
+++ b/android/support/v4/app/FragmentActivity.java
@@ -536,7 +536,7 @@
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+        markFragmentsCreated();
         Parcelable p = mFragments.saveAllState();
         if (p != null) {
             outState.putParcelable(FRAGMENTS_TAG, p);
@@ -591,7 +591,7 @@
         super.onStop();
 
         mStopped = true;
-        markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+        markFragmentsCreated();
         mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
 
         mFragments.dispatchStop();
@@ -970,18 +970,30 @@
         }
     }
 
-    private static void markState(FragmentManager manager, Lifecycle.State state) {
+    private void markFragmentsCreated() {
+        boolean reiterate;
+        do {
+            reiterate = markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+        } while (reiterate);
+    }
+
+    private static boolean markState(FragmentManager manager, Lifecycle.State state) {
+        boolean hadNotMarked = false;
         Collection<Fragment> fragments = manager.getFragments();
         for (Fragment fragment : fragments) {
             if (fragment == null) {
                 continue;
             }
-            fragment.mLifecycleRegistry.markState(state);
+            if (fragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
+                fragment.mLifecycleRegistry.markState(state);
+                hadNotMarked = true;
+            }
 
             FragmentManager childFragmentManager = fragment.peekChildFragmentManager();
             if (childFragmentManager != null) {
-                markState(childFragmentManager, state);
+                hadNotMarked |= markState(childFragmentManager, state);
             }
         }
+        return hadNotMarked;
     }
 }
diff --git a/android/support/v4/graphics/TypefaceCompat.java b/android/support/v4/graphics/TypefaceCompat.java
index 3c55df6..734f183 100644
--- a/android/support/v4/graphics/TypefaceCompat.java
+++ b/android/support/v4/graphics/TypefaceCompat.java
@@ -35,7 +35,6 @@
 import android.support.v4.provider.FontsContractCompat;
 import android.support.v4.provider.FontsContractCompat.FontInfo;
 import android.support.v4.util.LruCache;
-
 /**
  * Helper for accessing features in {@link Typeface}.
  * @hide
@@ -46,7 +45,9 @@
 
     private static final TypefaceCompatImpl sTypefaceCompatImpl;
     static {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            sTypefaceCompatImpl = new TypefaceCompatApi28Impl();
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             sTypefaceCompatImpl = new TypefaceCompatApi26Impl();
         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                 && TypefaceCompatApi24Impl.isUsable()) {
diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 1b55a2e..00e31a1 100644
--- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -60,76 +60,69 @@
             "createFromFamiliesWithDefault";
     private static final String FREEZE_METHOD = "freeze";
     private static final String ABORT_CREATION_METHOD = "abortCreation";
-    private static final Class sFontFamily;
-    private static final Constructor sFontFamilyCtor;
-    private static final Method sAddFontFromAssetManager;
-    private static final Method sAddFontFromBuffer;
-    private static final Method sFreeze;
-    private static final Method sAbortCreation;
-    private static final Method sCreateFromFamiliesWithDefault;
     private static final int RESOLVE_BY_FONT_TABLE = -1;
 
-    static {
-        Class fontFamilyClass;
+    protected final Class mFontFamily;
+    protected final Constructor mFontFamilyCtor;
+    protected final Method mAddFontFromAssetManager;
+    protected final Method mAddFontFromBuffer;
+    protected final Method mFreeze;
+    protected final Method mAbortCreation;
+    protected final Method mCreateFromFamiliesWithDefault;
+
+    public TypefaceCompatApi26Impl() {
+        Class fontFamily;
         Constructor fontFamilyCtor;
-        Method addFontMethod;
-        Method addFromBufferMethod;
-        Method freezeMethod;
-        Method abortCreationMethod;
-        Method createFromFamiliesWithDefaultMethod;
+        Method addFontFromAssetManager;
+        Method addFontFromBuffer;
+        Method freeze;
+        Method abortCreation;
+        Method createFromFamiliesWithDefault;
         try {
-            fontFamilyClass = Class.forName(FONT_FAMILY_CLASS);
-            fontFamilyCtor = fontFamilyClass.getConstructor();
-            addFontMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
-                    AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
-                    Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
-            addFromBufferMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
-                    ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
-                    Integer.TYPE);
-            freezeMethod = fontFamilyClass.getMethod(FREEZE_METHOD);
-            abortCreationMethod = fontFamilyClass.getMethod(ABORT_CREATION_METHOD);
-            Object familyArray = Array.newInstance(fontFamilyClass, 1);
-            createFromFamiliesWithDefaultMethod =
-                    Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
-                            familyArray.getClass(), Integer.TYPE, Integer.TYPE);
-            createFromFamiliesWithDefaultMethod.setAccessible(true);
+            fontFamily = obtainFontFamily();
+            fontFamilyCtor = obtainFontFamilyCtor(fontFamily);
+            addFontFromAssetManager = obtainAddFontFromAssetManagerMethod(fontFamily);
+            addFontFromBuffer = obtainAddFontFromBufferMethod(fontFamily);
+            freeze = obtainFreezeMethod(fontFamily);
+            abortCreation = obtainAbortCreationMethod(fontFamily);
+            createFromFamiliesWithDefault = obtainCreateFromFamiliesWithDefaultMethod(fontFamily);
         } catch (ClassNotFoundException | NoSuchMethodException e) {
             Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(),
                     e);
-            fontFamilyClass = null;
+            fontFamily = null;
             fontFamilyCtor = null;
-            addFontMethod = null;
-            addFromBufferMethod = null;
-            freezeMethod = null;
-            abortCreationMethod = null;
-            createFromFamiliesWithDefaultMethod = null;
+            addFontFromAssetManager = null;
+            addFontFromBuffer = null;
+            freeze = null;
+            abortCreation = null;
+            createFromFamiliesWithDefault = null;
         }
-        sFontFamilyCtor = fontFamilyCtor;
-        sFontFamily = fontFamilyClass;
-        sAddFontFromAssetManager = addFontMethod;
-        sAddFontFromBuffer = addFromBufferMethod;
-        sFreeze = freezeMethod;
-        sAbortCreation = abortCreationMethod;
-        sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod;
+        mFontFamily = fontFamily;
+        mFontFamilyCtor = fontFamilyCtor;
+        mAddFontFromAssetManager = addFontFromAssetManager;
+        mAddFontFromBuffer = addFontFromBuffer;
+        mFreeze = freeze;
+        mAbortCreation = abortCreation;
+        mCreateFromFamiliesWithDefault = createFromFamiliesWithDefault;
     }
 
     /**
-     * Returns true if API26 implementation is usable.
+     * Returns true if all the necessary methods were found.
      */
-    private static boolean isFontFamilyPrivateAPIAvailable() {
-        if (sAddFontFromAssetManager == null) {
+    private boolean isFontFamilyPrivateAPIAvailable() {
+        if (mAddFontFromAssetManager == null) {
             Log.w(TAG, "Unable to collect necessary private methods. "
                     + "Fallback to legacy implementation.");
         }
-        return sAddFontFromAssetManager != null;
+        return mAddFontFromAssetManager != null;
     }
 
     /**
      * Create a new FontFamily instance
      */
-    private static Object newFamily() {
+    private Object newFamily() {
         try {
-            return sFontFamilyCtor.newInstance();
+            return mFontFamilyCtor.newInstance();
         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
             throw new RuntimeException(e);
         }
@@ -139,10 +132,10 @@
      * Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie,
      *      boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes)
      */
-    private static boolean addFontFromAssetManager(Context context, Object family, String fileName,
+    private boolean addFontFromAssetManager(Context context, Object family, String fileName,
             int ttcIndex, int weight, int style) {
         try {
-            final Boolean result = (Boolean) sAddFontFromAssetManager.invoke(family,
+            final Boolean result = (Boolean) mAddFontFromAssetManager.invoke(family,
                     context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex,
                     weight, style, null /* axes */);
             return result.booleanValue();
@@ -155,10 +148,10 @@
      * Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
      *      int weight, int italic)
      */
-    private static boolean addFontFromBuffer(Object family, ByteBuffer buffer,
+    private boolean addFontFromBuffer(Object family, ByteBuffer buffer,
             int ttcIndex, int weight, int style) {
         try {
-            final Boolean result = (Boolean) sAddFontFromBuffer.invoke(family,
+            final Boolean result = (Boolean) mAddFontFromBuffer.invoke(family,
                     buffer, ttcIndex, null /* axes */, weight, style);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
@@ -167,14 +160,14 @@
     }
 
     /**
-     * Call static method Typeface#createFromFamiliesWithDefault(
+     * Call method Typeface#createFromFamiliesWithDefault(
      *      FontFamily[] families, int weight, int italic)
      */
-    private static Typeface createFromFamiliesWithDefault(Object family) {
+    protected Typeface createFromFamiliesWithDefault(Object family) {
         try {
-            Object familyArray = Array.newInstance(sFontFamily, 1);
+            Object familyArray = Array.newInstance(mFontFamily, 1);
             Array.set(familyArray, 0, family);
-            return (Typeface) sCreateFromFamiliesWithDefault.invoke(null /* static method */,
+            return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
                     familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -184,9 +177,9 @@
     /**
      * Call FontFamily#freeze()
      */
-    private static boolean freeze(Object family) {
+    private boolean freeze(Object family) {
         try {
-            Boolean result = (Boolean) sFreeze.invoke(family);
+            Boolean result = (Boolean) mFreeze.invoke(family);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -196,9 +189,9 @@
     /**
      * Call FontFamily#abortCreation()
      */
-    private static boolean abortCreation(Object family) {
+    private boolean abortCreation(Object family) {
         try {
-            Boolean result = (Boolean) sAbortCreation.invoke(family);
+            Boolean result = (Boolean) mAbortCreation.invoke(family);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -299,4 +292,47 @@
         }
         return createFromFamiliesWithDefault(fontFamily);
     }
+
+    // The following getters retrieve by reflection the Typeface methods, belonging to the
+    // framework code, which will be invoked. Since the definitions of these methods can change
+    // across different API versions, inheriting classes should override these getters in order to
+    // reflect the method definitions in the API versions they represent.
+    //===========================================================================================
+    protected Class obtainFontFamily() throws ClassNotFoundException {
+        return Class.forName(FONT_FAMILY_CLASS);
+    }
+
+    protected Constructor obtainFontFamilyCtor(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getConstructor();
+    }
+
+    protected Method obtainAddFontFromAssetManagerMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        return fontFamily.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
+                AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
+                Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
+    }
+
+    protected Method obtainAddFontFromBufferMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
+                ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
+                Integer.TYPE);
+    }
+
+    protected Method obtainFreezeMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(FREEZE_METHOD);
+    }
+
+    protected Method obtainAbortCreationMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(ABORT_CREATION_METHOD);
+    }
+
+    protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        Object familyArray = Array.newInstance(fontFamily, 1);
+        Method m =  Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+                familyArray.getClass(), Integer.TYPE, Integer.TYPE);
+        m.setAccessible(true);
+        return m;
+    }
 }
diff --git a/android/support/v4/graphics/TypefaceCompatApi28Impl.java b/android/support/v4/graphics/TypefaceCompatApi28Impl.java
new file mode 100644
index 0000000..baa2ce6
--- /dev/null
+++ b/android/support/v4/graphics/TypefaceCompatApi28Impl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Typeface;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Implementation of the Typeface compat methods for API 28 and above.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@RequiresApi(28)
+public class TypefaceCompatApi28Impl extends TypefaceCompatApi26Impl {
+    private static final String TAG = "TypefaceCompatApi28Impl";
+
+    private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD =
+            "createFromFamiliesWithDefault";
+    private static final int RESOLVE_BY_FONT_TABLE = -1;
+    private static final String DEFAULT_FAMILY = "sans-serif";
+
+    /**
+     * Call method Typeface#createFromFamiliesWithDefault(
+     *      FontFamily[] families, String fallbackName, int weight, int italic)
+     */
+    @Override
+    protected Typeface createFromFamiliesWithDefault(Object family) {
+        try {
+            Object familyArray = Array.newInstance(mFontFamily, 1);
+            Array.set(familyArray, 0, family);
+            return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
+                    familyArray, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        Object familyArray = Array.newInstance(fontFamily, 1);
+        Method m =  Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+                familyArray.getClass(), String.class, Integer.TYPE, Integer.TYPE);
+        m.setAccessible(true);
+        return m;
+    }
+}
diff --git a/android/support/v4/media/MediaBrowserCompat.java b/android/support/v4/media/MediaBrowserCompat.java
index 85f5a51..7adf7d7 100644
--- a/android/support/v4/media/MediaBrowserCompat.java
+++ b/android/support/v4/media/MediaBrowserCompat.java
@@ -676,17 +676,15 @@
         WeakReference<Subscription> mSubscriptionRef;
 
         public SubscriptionCallback() {
+            mToken = new Binder();
             if (Build.VERSION.SDK_INT >= 26) {
                 mSubscriptionCallbackObj =
                         MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26());
-                mToken = null;
             } else if (Build.VERSION.SDK_INT >= 21) {
                 mSubscriptionCallbackObj =
                         MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
-                mToken = new Binder();
             } else {
                 mSubscriptionCallbackObj = null;
-                mToken = new Binder();
             }
         }
 
@@ -1958,22 +1956,30 @@
         @Override
         public void subscribe(@NonNull String parentId, @Nullable Bundle options,
                 @NonNull SubscriptionCallback callback) {
-            if (options == null) {
-                MediaBrowserCompatApi21.subscribe(
-                        mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+            if (mServiceBinderWrapper == null) {
+                if (options == null) {
+                    MediaBrowserCompatApi21.subscribe(
+                            mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+                } else {
+                    MediaBrowserCompatApi26.subscribe(
+                            mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+                }
             } else {
-                MediaBrowserCompatApi26.subscribe(
-                        mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+                super.subscribe(parentId, options, callback);
             }
         }
 
         @Override
         public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
-            if (callback == null) {
-                MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+            if (mServiceBinderWrapper == null) {
+                if (callback == null) {
+                    MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+                } else {
+                    MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
+                            callback.mSubscriptionCallbackObj);
+                }
             } else {
-                MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
-                        callback.mSubscriptionCallbackObj);
+                super.unsubscribe(parentId, callback);
             }
         }
     }
diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java
index 53b111a..debc66e 100644
--- a/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -422,11 +422,15 @@
 
         @Override
         public void notifyChildrenChanged(final String parentId, final Bundle options) {
-            if (options == null) {
-                MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+            if (mMessenger == null) {
+                if (options == null) {
+                    MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+                } else {
+                    MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
+                            options);
+                }
             } else {
-                MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
-                        options);
+                super.notifyChildrenChanged(parentId, options);
             }
         }
 
diff --git a/android/support/v4/media/MediaMetadataCompat.java b/android/support/v4/media/MediaMetadataCompat.java
index 3ddf255..00f16cb 100644
--- a/android/support/v4/media/MediaMetadataCompat.java
+++ b/android/support/v4/media/MediaMetadataCompat.java
@@ -365,10 +365,12 @@
 
     MediaMetadataCompat(Bundle bundle) {
         mBundle = new Bundle(bundle);
+        mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
     }
 
     MediaMetadataCompat(Parcel in) {
         mBundle = in.readBundle();
+        mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
     }
 
     /**
diff --git a/android/support/v4/view/ViewCompat.java b/android/support/v4/view/ViewCompat.java
index 34a198a..204a121 100644
--- a/android/support/v4/view/ViewCompat.java
+++ b/android/support/v4/view/ViewCompat.java
@@ -1356,7 +1356,7 @@
                 // after applying the tint
                 Drawable background = view.getBackground();
                 boolean hasTint = (view.getBackgroundTintList() != null)
-                        && (view.getBackgroundTintMode() != null);
+                        || (view.getBackgroundTintMode() != null);
                 if ((background != null) && hasTint) {
                     if (background.isStateful()) {
                         background.setState(view.getDrawableState());
@@ -1375,7 +1375,7 @@
                 // after applying the tint
                 Drawable background = view.getBackground();
                 boolean hasTint = (view.getBackgroundTintList() != null)
-                        && (view.getBackgroundTintMode() != null);
+                        || (view.getBackgroundTintMode() != null);
                 if ((background != null) && hasTint) {
                     if (background.isStateful()) {
                         background.setState(view.getDrawableState());
diff --git a/android/support/v7/app/AppCompatDelegateImplV9.java b/android/support/v7/app/AppCompatDelegateImplV9.java
index 056e33e..5b53401 100644
--- a/android/support/v7/app/AppCompatDelegateImplV9.java
+++ b/android/support/v7/app/AppCompatDelegateImplV9.java
@@ -1001,7 +1001,26 @@
     public View createView(View parent, final String name, @NonNull Context context,
             @NonNull AttributeSet attrs) {
         if (mAppCompatViewInflater == null) {
-            mAppCompatViewInflater = new AppCompatViewInflater();
+            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+            String viewInflaterClassName =
+                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
+            if ((viewInflaterClassName == null)
+                    || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
+                // Either default class name or set explicitly to null. In both cases
+                // create the base inflater (no reflection)
+                mAppCompatViewInflater = new AppCompatViewInflater();
+            } else {
+                try {
+                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
+                    mAppCompatViewInflater =
+                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
+                                    .newInstance();
+                } catch (Throwable t) {
+                    Log.i(TAG, "Failed to instantiate custom view inflater "
+                            + viewInflaterClassName + ". Falling back to default.", t);
+                    mAppCompatViewInflater = new AppCompatViewInflater();
+                }
+            }
         }
 
         boolean inheritContext = false;
diff --git a/android/support/v7/app/AppCompatViewInflater.java b/android/support/v7/app/AppCompatViewInflater.java
index 54d01bc..87a1a3c 100644
--- a/android/support/v7/app/AppCompatViewInflater.java
+++ b/android/support/v7/app/AppCompatViewInflater.java
@@ -51,14 +51,12 @@
 import java.util.Map;
 
 /**
- * This class is responsible for manually inflating our tinted widgets which are used on devices
- * running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class
- * should only be used when running on those devices.
+ * This class is responsible for manually inflating our tinted widgets.
  * <p>This class two main responsibilities: the first is to 'inject' our tinted views in place of
  * the framework versions in layout inflation; the second is backport the {@code android:theme}
  * functionality for any inflated widgets. This include theme inheritance from its parent.
  */
-class AppCompatViewInflater {
+public class AppCompatViewInflater {
 
     private static final Class<?>[] sConstructorSignature = new Class[]{
             Context.class, AttributeSet.class};
@@ -77,7 +75,7 @@
 
     private final Object[] mConstructorArgs = new Object[2];
 
-    public final View createView(View parent, final String name, @NonNull Context context,
+    final View createView(View parent, final String name, @NonNull Context context,
             @NonNull AttributeSet attrs, boolean inheritContext,
             boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
         final Context originalContext = context;
@@ -100,44 +98,63 @@
         // We need to 'inject' our tint aware Views in place of the standard framework versions
         switch (name) {
             case "TextView":
-                view = new AppCompatTextView(context, attrs);
+                view = createTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "ImageView":
-                view = new AppCompatImageView(context, attrs);
+                view = createImageView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "Button":
-                view = new AppCompatButton(context, attrs);
+                view = createButton(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "EditText":
-                view = new AppCompatEditText(context, attrs);
+                view = createEditText(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "Spinner":
-                view = new AppCompatSpinner(context, attrs);
+                view = createSpinner(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "ImageButton":
-                view = new AppCompatImageButton(context, attrs);
+                view = createImageButton(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "CheckBox":
-                view = new AppCompatCheckBox(context, attrs);
+                view = createCheckBox(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "RadioButton":
-                view = new AppCompatRadioButton(context, attrs);
+                view = createRadioButton(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "CheckedTextView":
-                view = new AppCompatCheckedTextView(context, attrs);
+                view = createCheckedTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "AutoCompleteTextView":
-                view = new AppCompatAutoCompleteTextView(context, attrs);
+                view = createAutoCompleteTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "MultiAutoCompleteTextView":
-                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
+                view = createMultiAutoCompleteTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "RatingBar":
-                view = new AppCompatRatingBar(context, attrs);
+                view = createRatingBar(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "SeekBar":
-                view = new AppCompatSeekBar(context, attrs);
+                view = createSeekBar(context, attrs);
+                verifyNotNull(view, name);
                 break;
+            default:
+                // The fallback that allows extending class to take over view inflation
+                // for other tags. Note that we don't check that the result is not-null.
+                // That allows the custom inflater path to fall back on the default one
+                // later in this method.
+                view = createView(context, name, attrs);
         }
 
         if (view == null && originalContext != context) {
@@ -154,6 +171,85 @@
         return view;
     }
 
+    @NonNull
+    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
+        return new AppCompatTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
+        return new AppCompatImageView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatButton createButton(Context context, AttributeSet attrs) {
+        return new AppCompatButton(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatEditText createEditText(Context context, AttributeSet attrs) {
+        return new AppCompatEditText(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatSpinner createSpinner(Context context, AttributeSet attrs) {
+        return new AppCompatSpinner(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) {
+        return new AppCompatImageButton(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatCheckBox createCheckBox(Context context, AttributeSet attrs) {
+        return new AppCompatCheckBox(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatRadioButton createRadioButton(Context context, AttributeSet attrs) {
+        return new AppCompatRadioButton(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatCheckedTextView createCheckedTextView(Context context, AttributeSet attrs) {
+        return new AppCompatCheckedTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatAutoCompleteTextView createAutoCompleteTextView(Context context,
+            AttributeSet attrs) {
+        return new AppCompatAutoCompleteTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(Context context,
+            AttributeSet attrs) {
+        return new AppCompatMultiAutoCompleteTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatRatingBar createRatingBar(Context context, AttributeSet attrs) {
+        return new AppCompatRatingBar(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatSeekBar createSeekBar(Context context, AttributeSet attrs) {
+        return new AppCompatSeekBar(context, attrs);
+    }
+
+    private void verifyNotNull(View view, String name) {
+        if (view == null) {
+            throw new IllegalStateException(this.getClass().getName()
+                    + " asked to inflate view for <" + name + ">, but returned null");
+        }
+    }
+
+    @Nullable
+    protected View createView(Context context, String name, AttributeSet attrs) {
+        return null;
+    }
+
     private View createViewFromTag(Context context, String name, AttributeSet attrs) {
         if (name.equals("view")) {
             name = attrs.getAttributeValue(null, "class");
@@ -165,14 +261,14 @@
 
             if (-1 == name.indexOf('.')) {
                 for (int i = 0; i < sClassPrefixList.length; i++) {
-                    final View view = createView(context, name, sClassPrefixList[i]);
+                    final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
                     if (view != null) {
                         return view;
                     }
                 }
                 return null;
             } else {
-                return createView(context, name, null);
+                return createViewByPrefix(context, name, null);
             }
         } catch (Exception e) {
             // We do not want to catch these, lets return null and let the actual LayoutInflater
@@ -209,7 +305,7 @@
         a.recycle();
     }
 
-    private View createView(Context context, String name, String prefix)
+    private View createViewByPrefix(Context context, String name, String prefix)
             throws ClassNotFoundException, InflateException {
         Constructor<? extends View> constructor = sConstructorMap.get(name);
 
diff --git a/android/support/v7/util/SortedList.java b/android/support/v7/util/SortedList.java
index c62d0ce..af000a1 100644
--- a/android/support/v7/util/SortedList.java
+++ b/android/support/v7/util/SortedList.java
@@ -16,6 +16,8 @@
 
 package android.support.v7.util;
 
+import android.support.annotation.Nullable;
+
 import java.lang.reflect.Array;
 import java.util.Arrays;
 import java.util.Collection;
@@ -315,7 +317,8 @@
                 newDataStart++;
                 mOldDataStart++;
                 if (!mCallback.areContentsTheSame(oldItem, newItem)) {
-                    mCallback.onChanged(mMergedSize - 1, 1);
+                    mCallback.onChanged(mMergedSize - 1, 1,
+                            mCallback.getChangePayload(oldItem, newItem));
                 }
             } else {
                 // Old item is lower than or equal to (but not the same as the new). Output it.
@@ -401,7 +404,7 @@
                     return index;
                 } else {
                     mData[index] = item;
-                    mCallback.onChanged(index, 1);
+                    mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
                     return index;
                 }
             }
@@ -488,13 +491,13 @@
             if (cmp == 0) {
                 mData[index] = item;
                 if (contentsChanged) {
-                    mCallback.onChanged(index, 1);
+                    mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
                 }
                 return;
             }
         }
         if (contentsChanged) {
-            mCallback.onChanged(index, 1);
+            mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
         }
         // TODO this done in 1 pass to avoid shifting twice.
         removeItemAtIndex(index, false);
@@ -741,6 +744,28 @@
          * @return True if the two items represent the same object or false if they are different.
          */
         abstract public boolean areItemsTheSame(T2 item1, T2 item2);
+
+        /**
+         * When {@link #areItemsTheSame(T2, T2)} returns {@code true} for two items and
+         * {@link #areContentsTheSame(T2, T2)} returns false for them, {@link Callback} calls this
+         * method to get a payload about the change.
+         * <p>
+         * For example, if you are using {@link Callback} with
+         * {@link android.support.v7.widget.RecyclerView}, you can return the particular field that
+         * changed in the item and your
+         * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+         * information to run the correct animation.
+         * <p>
+         * Default implementation returns {@code null}.
+         *
+         * @param item1 The first item to check.
+         * @param item2 The second item to check.
+         * @return A payload object that represents the changes between the two items.
+         */
+        @Nullable
+        public Object getChangePayload(T2 item1, T2 item2) {
+            return null;
+        }
     }
 
     /**
@@ -801,6 +826,11 @@
         }
 
         @Override
+        public void onChanged(int position, int count, Object payload) {
+            mBatchingListUpdateCallback.onChanged(position, count, payload);
+        }
+
+        @Override
         public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
             return mWrappedCallback.areContentsTheSame(oldItem, newItem);
         }
@@ -810,6 +840,12 @@
             return mWrappedCallback.areItemsTheSame(item1, item2);
         }
 
+        @Nullable
+        @Override
+        public Object getChangePayload(T2 item1, T2 item2) {
+            return mWrappedCallback.getChangePayload(item1, item2);
+        }
+
         /**
          * This method dispatches any pending event notifications to the wrapped Callback.
          * You <b>must</b> always call this method after you are done with editing the SortedList.
diff --git a/android/support/v7/util/SortedListBatchedCallbackTest.java b/android/support/v7/util/SortedListBatchedCallbackTest.java
index 3ace217..bc50415 100644
--- a/android/support/v7/util/SortedListBatchedCallbackTest.java
+++ b/android/support/v7/util/SortedListBatchedCallbackTest.java
@@ -50,6 +50,16 @@
     }
 
     @Test
+    public void onChangeWithPayload() {
+        final Object payload = 7;
+        mBatchedCallback.onChanged(1, 2, payload);
+        verifyZeroInteractions(mMockCallback);
+        mBatchedCallback.dispatchLastEvent();
+        verify(mMockCallback).onChanged(1, 2, payload);
+        verifyNoMoreInteractions(mMockCallback);
+    }
+
+    @Test
     public void onRemoved() {
         mBatchedCallback.onRemoved(2, 3);
         verifyZeroInteractions(mMockCallback);
diff --git a/android/support/v7/util/SortedListTest.java b/android/support/v7/util/SortedListTest.java
index da3c957..47d2ac0 100644
--- a/android/support/v7/util/SortedListTest.java
+++ b/android/support/v7/util/SortedListTest.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.util;
 
+import android.support.annotation.Nullable;
 import android.support.test.filters.SmallTest;
 
 import junit.framework.TestCase;
@@ -41,6 +42,8 @@
     List<Pair> mRemovals = new ArrayList<Pair>();
     List<Pair> mMoves = new ArrayList<Pair>();
     List<Pair> mUpdates = new ArrayList<Pair>();
+    private boolean mPayloadChanges = false;
+    List<PayloadChange> mPayloadUpdates = new ArrayList<>();
     private SortedList.Callback<Item> mCallback;
     InsertedCallback<Item> mInsertedCallback;
     ChangedCallback<Item> mChangedCallback;
@@ -97,6 +100,15 @@
             }
 
             @Override
+            public void onChanged(int position, int count, Object payload) {
+                if (mPayloadChanges) {
+                    mPayloadUpdates.add(new PayloadChange(position, count, payload));
+                } else {
+                    onChanged(position, count);
+                }
+            }
+
+            @Override
             public boolean areContentsTheSame(Item oldItem, Item newItem) {
                 return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data;
             }
@@ -105,6 +117,15 @@
             public boolean areItemsTheSame(Item item1, Item item2) {
                 return item1.id == item2.id;
             }
+
+            @Nullable
+            @Override
+            public Object getChangePayload(Item item1, Item item2) {
+                if (mPayloadChanges) {
+                    return item2.data;
+                }
+                return null;
+            }
         };
         mInsertedCallback = null;
         mChangedCallback = null;
@@ -705,6 +726,76 @@
         assertTrue(mAdditions.contains(new Pair(0, 6)));
     }
 
+    @Test
+    public void testAddExistingItemCallsChangeWithPayload() {
+        mList.addAll(
+                new Item(1, 10),
+                new Item(2, 20),
+                new Item(3, 30)
+        );
+        mPayloadChanges = true;
+
+        // add an item with the same id but a new data field i.e. send an update
+        final Item twoUpdate = new Item(2, 20);
+        twoUpdate.data = 1337;
+        mList.add(twoUpdate);
+        assertEquals(1, mPayloadUpdates.size());
+        final PayloadChange update = mPayloadUpdates.get(0);
+        assertEquals(1, update.position);
+        assertEquals(1, update.count);
+        assertEquals(1337, update.payload);
+        assertEquals(3, size());
+    }
+
+    @Test
+    public void testUpdateItemCallsChangeWithPayload() {
+        mList.addAll(
+                new Item(1, 10),
+                new Item(2, 20),
+                new Item(3, 30)
+        );
+        mPayloadChanges = true;
+
+        // add an item with the same id but a new data field i.e. send an update
+        final Item twoUpdate = new Item(2, 20);
+        twoUpdate.data = 1337;
+        mList.updateItemAt(1, twoUpdate);
+        assertEquals(1, mPayloadUpdates.size());
+        final PayloadChange update = mPayloadUpdates.get(0);
+        assertEquals(1, update.position);
+        assertEquals(1, update.count);
+        assertEquals(1337, update.payload);
+        assertEquals(3, size());
+        assertEquals(1337, mList.get(1).data);
+    }
+
+    @Test
+    public void testAddMultipleExistingItemCallsChangeWithPayload() {
+        mList.addAll(
+                new Item(1, 10),
+                new Item(2, 20),
+                new Item(3, 30)
+        );
+        mPayloadChanges = true;
+
+        // add two items with the same ids but a new data fields i.e. send two updates
+        final Item twoUpdate = new Item(2, 20);
+        twoUpdate.data = 222;
+        final Item threeUpdate = new Item(3, 30);
+        threeUpdate.data = 333;
+        mList.addAll(twoUpdate, threeUpdate);
+        assertEquals(2, mPayloadUpdates.size());
+        final PayloadChange update1 = mPayloadUpdates.get(0);
+        assertEquals(1, update1.position);
+        assertEquals(1, update1.count);
+        assertEquals(222, update1.payload);
+        final PayloadChange update2 = mPayloadUpdates.get(1);
+        assertEquals(2, update2.position);
+        assertEquals(1, update2.count);
+        assertEquals(333, update2.payload);
+        assertEquals(3, size());
+    }
+
     private int size() {
         return mList.size();
     }
@@ -821,4 +912,37 @@
             return result;
         }
     }
+
+    private static final class PayloadChange {
+        public final int position;
+        public final int count;
+        public final Object payload;
+
+        PayloadChange(int position, int count, Object payload) {
+            this.position = position;
+            this.count = count;
+            this.payload = payload;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            PayloadChange payloadChange = (PayloadChange) o;
+
+            if (position != payloadChange.position) return false;
+            if (count != payloadChange.count) return false;
+            return payload != null ? payload.equals(payloadChange.payload)
+                    : payloadChange.payload == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = position;
+            result = 31 * result + count;
+            result = 31 * result + (payload != null ? payload.hashCode() : 0);
+            return result;
+        }
+    }
 }
\ No newline at end of file
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index fa6196f..b8ce82a 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -213,9 +213,9 @@
         if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
                 || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
             mFontTypeface = null;
-            int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
-                    ? R.styleable.TextAppearance_android_fontFamily
-                    : R.styleable.TextAppearance_fontFamily;
+            int fontFamilyId = a.hasValue(R.styleable.TextAppearance_fontFamily)
+                    ? R.styleable.TextAppearance_fontFamily
+                    : R.styleable.TextAppearance_android_fontFamily;
             if (!context.isRestricted()) {
                 final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
                 ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
diff --git a/android/support/v7/widget/TooltipCompatHandler.java b/android/support/v7/widget/TooltipCompatHandler.java
index 5ce1f8b..63a6198 100644
--- a/android/support/v7/widget/TooltipCompatHandler.java
+++ b/android/support/v7/widget/TooltipCompatHandler.java
@@ -66,6 +66,10 @@
     private TooltipPopup mPopup;
     private boolean mFromTouch;
 
+    // The handler currently scheduled to show a tooltip, triggered by a hover
+    // (there can be only one).
+    private static TooltipCompatHandler sPendingHandler;
+
     // The handler currently showing a tooltip (there can be only one).
     private static TooltipCompatHandler sActiveHandler;
 
@@ -76,6 +80,15 @@
      * @param tooltipText the tooltip text
      */
     public static void setTooltipText(View view, CharSequence tooltipText) {
+        // The code below is not attempting to update the tooltip text
+        // for a pending or currently active tooltip, because it may lead
+        // to updating the wrong tooltip in in some rare cases (e.g. when
+        // action menu item views are recycled). Instead, the tooltip is
+        // canceled/hidden. This might still be the wrong tooltip,
+        // but hiding a wrong tooltip is less disruptive UX.
+        if (sPendingHandler != null && sPendingHandler.mAnchor == view) {
+            setPendingHandler(null);
+        }
         if (TextUtils.isEmpty(tooltipText)) {
             if (sActiveHandler != null && sActiveHandler.mAnchor == view) {
                 sActiveHandler.hide();
@@ -119,8 +132,7 @@
                 if (mAnchor.isEnabled() && mPopup == null) {
                     mAnchorX = (int) event.getX();
                     mAnchorY = (int) event.getY();
-                    mAnchor.removeCallbacks(mShowRunnable);
-                    mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+                    setPendingHandler(this);
                 }
                 break;
             case MotionEvent.ACTION_HOVER_EXIT:
@@ -145,6 +157,7 @@
         if (!ViewCompat.isAttachedToWindow(mAnchor)) {
             return;
         }
+        setPendingHandler(null);
         if (sActiveHandler != null) {
             sActiveHandler.hide();
         }
@@ -180,7 +193,27 @@
                 Log.e(TAG, "sActiveHandler.mPopup == null");
             }
         }
-        mAnchor.removeCallbacks(mShowRunnable);
+        if (sPendingHandler == this) {
+            setPendingHandler(null);
+        }
         mAnchor.removeCallbacks(mHideRunnable);
     }
+
+    private static void setPendingHandler(TooltipCompatHandler handler) {
+        if (sPendingHandler != null) {
+            sPendingHandler.cancelPendingShow();
+        }
+        sPendingHandler = handler;
+        if (sPendingHandler != null) {
+            sPendingHandler.scheduleShow();
+        }
+    }
+
+    private void scheduleShow() {
+        mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+    }
+
+    private void cancelPendingShow() {
+        mAnchor.removeCallbacks(mShowRunnable);
+    }
 }
diff --git a/android/support/v7/widget/util/SortedListAdapterCallback.java b/android/support/v7/widget/util/SortedListAdapterCallback.java
index 4921541..a1203a6 100644
--- a/android/support/v7/widget/util/SortedListAdapterCallback.java
+++ b/android/support/v7/widget/util/SortedListAdapterCallback.java
@@ -56,4 +56,9 @@
     public void onChanged(int position, int count) {
         mAdapter.notifyItemRangeChanged(position, count);
     }
+
+    @Override
+    public void onChanged(int position, int count, Object payload) {
+        mAdapter.notifyItemRangeChanged(position, count, payload);
+    }
 }
diff --git a/android/support/wear/ambient/AmbientDelegate.java b/android/support/wear/ambient/AmbientDelegate.java
index 4901290..8e96a02 100644
--- a/android/support/wear/ambient/AmbientDelegate.java
+++ b/android/support/wear/ambient/AmbientDelegate.java
@@ -19,14 +19,12 @@
 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.
@@ -146,19 +144,6 @@
     }
 
     /**
-     * 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() {
@@ -177,31 +162,4 @@
             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
index db53dfc..5db9383 100644
--- a/android/support/wear/ambient/AmbientMode.java
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -38,13 +38,13 @@
  * 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.
+ * can be used to query the current status of the ambient mode.
  * 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);
+ *     boolean isAmbient =  controller.isAmbient();
  * }</pre>
  */
 public final class AmbientMode extends Fragment {
@@ -117,7 +117,7 @@
          * Called when the system is updating the display for ambient mode. Activities may use this
          * opportunity to update or invalidate views.
          */
-        public void onUpdateAmbient() {};
+        public void onUpdateAmbient() {}
 
         /**
          * Called when an activity should exit ambient mode. This event is sent while an activity is
@@ -126,7 +126,7 @@
          * <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() {};
+        public void onExitAmbient() {}
     }
 
     private final AmbientDelegate.AmbientCallback mCallback =
@@ -220,7 +220,7 @@
      * @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.
+     * ambient mode.
      */
     public static <T extends Activity & AmbientCallbackProvider> AmbientController
             attachAmbientSupport(T activity) {
@@ -251,9 +251,8 @@
 
     /**
      * 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}.
+     * query the current state of ambient mode. 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";
diff --git a/android/support/wear/ambient/SharedLibraryVersion.java b/android/support/wear/ambient/SharedLibraryVersion.java
index cd90a3b..9421d9e 100644
--- a/android/support/wear/ambient/SharedLibraryVersion.java
+++ b/android/support/wear/ambient/SharedLibraryVersion.java
@@ -16,7 +16,6 @@
 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;
@@ -24,10 +23,7 @@
 /**
  * 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() {
diff --git a/android/support/wear/ambient/WearableControllerProvider.java b/android/support/wear/ambient/WearableControllerProvider.java
index 1682dc0..4b6ae8e 100644
--- a/android/support/wear/ambient/WearableControllerProvider.java
+++ b/android/support/wear/ambient/WearableControllerProvider.java
@@ -28,7 +28,7 @@
  *
  * @hide
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public class WearableControllerProvider {
 
     private static final String TAG = "WearableControllerProvider";
diff --git a/android/support/wear/internal/widget/ResourcesUtil.java b/android/support/wear/internal/widget/ResourcesUtil.java
index f23a688..8ba3adf 100644
--- a/android/support/wear/internal/widget/ResourcesUtil.java
+++ b/android/support/wear/internal/widget/ResourcesUtil.java
@@ -26,7 +26,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public final class ResourcesUtil {
 
     /**
diff --git a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
index ad56048..4a7ce66 100644
--- a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
+++ b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
@@ -28,7 +28,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class MultiPagePresenter extends WearableNavigationDrawerPresenter {
 
     private final Ui mUi;
diff --git a/android/support/wear/internal/widget/drawer/MultiPageUi.java b/android/support/wear/internal/widget/drawer/MultiPageUi.java
index 9056845..0ba2f5d 100644
--- a/android/support/wear/internal/widget/drawer/MultiPageUi.java
+++ b/android/support/wear/internal/widget/drawer/MultiPageUi.java
@@ -16,6 +16,7 @@
 
 package android.support.wear.internal.widget.drawer;
 
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
@@ -37,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class MultiPageUi implements MultiPagePresenter.Ui {
 
     private static final String TAG = "MultiPageUi";
@@ -62,13 +63,8 @@
         final View content = inflater.inflate(R.layout.ws_navigation_drawer_view, drawer,
                 false /* attachToRoot */);
 
-        mNavigationPager =
-                (ViewPager) content
-                        .findViewById(R.id.ws_navigation_drawer_view_pager);
-        mPageIndicatorView =
-                (PageIndicatorView)
-                        content.findViewById(
-                                R.id.ws_navigation_drawer_page_indicator);
+        mNavigationPager = content.findViewById(R.id.ws_navigation_drawer_view_pager);
+        mPageIndicatorView = content.findViewById(R.id.ws_navigation_drawer_page_indicator);
 
         drawer.setDrawerContent(content);
     }
@@ -132,8 +128,9 @@
             mAdapter = adapter;
         }
 
+        @NonNull
         @Override
-        public Object instantiateItem(ViewGroup container, int position) {
+        public Object instantiateItem(@NonNull ViewGroup container, int position) {
             // Do not attach to root in the inflate method. The view needs to returned at the end
             // of this method. Attaching to root will cause view to point to container instead.
             final View view =
@@ -141,17 +138,17 @@
                             .inflate(R.layout.ws_navigation_drawer_item_view, container, false);
             container.addView(view);
             final ImageView iconView =
-                    (ImageView) view
-                            .findViewById(R.id.ws_navigation_drawer_item_icon);
+                    view.findViewById(R.id.ws_navigation_drawer_item_icon);
             final TextView textView =
-                    (TextView) view.findViewById(R.id.ws_navigation_drawer_item_text);
+                    view.findViewById(R.id.ws_navigation_drawer_item_text);
             iconView.setImageDrawable(mAdapter.getItemDrawable(position));
             textView.setText(mAdapter.getItemText(position));
             return view;
         }
 
         @Override
-        public void destroyItem(ViewGroup container, int position, Object object) {
+        public void destroyItem(@NonNull ViewGroup container, int position,
+                @NonNull Object object) {
             container.removeView((View) object);
         }
 
@@ -161,12 +158,12 @@
         }
 
         @Override
-        public int getItemPosition(Object object) {
+        public int getItemPosition(@NonNull Object object) {
             return POSITION_NONE;
         }
 
         @Override
-        public boolean isViewFromObject(View view, Object object) {
+        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
             return view == object;
         }
     }
diff --git a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
index d90b589..42cc7d0 100644
--- a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
+++ b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
@@ -29,7 +29,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class SinglePagePresenter extends WearableNavigationDrawerPresenter {
 
     private static final long DRAWER_CLOSE_DELAY_MS = 500;
diff --git a/android/support/wear/internal/widget/drawer/SinglePageUi.java b/android/support/wear/internal/widget/drawer/SinglePageUi.java
index f3a4290..ffc966f 100644
--- a/android/support/wear/internal/widget/drawer/SinglePageUi.java
+++ b/android/support/wear/internal/widget/drawer/SinglePageUi.java
@@ -38,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class SinglePageUi implements SinglePagePresenter.Ui {
 
     @IdRes
@@ -111,11 +111,10 @@
                         R.layout.ws_single_page_nav_drawer_peek_view, mDrawer,
                         false /* attachToRoot */);
 
-        mTextView = (TextView) content.findViewById(R.id.ws_nav_drawer_text);
+        mTextView = content.findViewById(R.id.ws_nav_drawer_text);
         mSinglePageImageViews = new CircledImageView[count];
         for (int i = 0; i < count; i++) {
-            mSinglePageImageViews[i] = (CircledImageView) content
-                    .findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
+            mSinglePageImageViews[i] = content.findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
             mSinglePageImageViews[i].setOnClickListener(new OnSelectedClickHandler(i, mPresenter));
             mSinglePageImageViews[i].setCircleHidden(true);
         }
diff --git a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
index 1c8c4fb..df108aa 100644
--- a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
+++ b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
@@ -30,7 +30,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public abstract class WearableNavigationDrawerPresenter {
 
     private final Set<OnItemSelectedListener> mOnItemSelectedListeners = new HashSet<>();
diff --git a/android/support/wear/utils/MetadataConstants.java b/android/support/wear/utils/MetadataConstants.java
index 5be9c52..c7335c2 100644
--- a/android/support/wear/utils/MetadataConstants.java
+++ b/android/support/wear/utils/MetadataConstants.java
@@ -15,16 +15,13 @@
  */
 package android.support.wear.utils;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.os.Build;
 
 /**
  * Constants for android wear apps which are related to manifest meta-data.
  */
-@TargetApi(Build.VERSION_CODES.N)
 public class MetadataConstants {
 
     //  Constants for standalone apps. //
diff --git a/android/support/wear/widget/BezierSCurveInterpolator.java b/android/support/wear/widget/BezierSCurveInterpolator.java
index 131bae8..9c56a83 100644
--- a/android/support/wear/widget/BezierSCurveInterpolator.java
+++ b/android/support/wear/widget/BezierSCurveInterpolator.java
@@ -17,8 +17,6 @@
 package android.support.wear.widget;
 
 import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.os.Build;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
 
@@ -27,8 +25,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
+@RestrictTo(Scope.LIBRARY)
 class BezierSCurveInterpolator implements TimeInterpolator {
 
     /**
diff --git a/android/support/wear/widget/BoxInsetLayout.java b/android/support/wear/widget/BoxInsetLayout.java
index ba35f2c..a8b1381 100644
--- a/android/support/wear/widget/BoxInsetLayout.java
+++ b/android/support/wear/widget/BoxInsetLayout.java
@@ -20,7 +20,6 @@
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -111,21 +110,6 @@
     }
 
     @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        insets = super.onApplyWindowInsets(insets);
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            final boolean round = insets.isRound();
-            if (round != mIsRound) {
-                mIsRound = round;
-                requestLayout();
-            }
-            mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
-                    insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
-        }
-        return insets;
-    }
-
-    @Override
     public void setForeground(Drawable drawable) {
         super.setForeground(drawable);
         mForegroundDrawable = drawable;
@@ -145,14 +129,10 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            requestApplyInsets();
-        } else {
-            mIsRound = getResources().getConfiguration().isScreenRound();
-            WindowInsets insets = getRootWindowInsets();
-            mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
-                    insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
-        }
+        mIsRound = getResources().getConfiguration().isScreenRound();
+        WindowInsets insets = getRootWindowInsets();
+        mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+                insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
     }
 
     @Override
@@ -413,7 +393,7 @@
     public static class LayoutParams extends FrameLayout.LayoutParams {
 
         /** @hide */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
         @IntDef({BOX_NONE, BOX_LEFT, BOX_TOP, BOX_RIGHT, BOX_BOTTOM, BOX_ALL})
         @Retention(RetentionPolicy.SOURCE)
         public @interface BoxedEdges {}
diff --git a/android/support/wear/widget/CircledImageView.java b/android/support/wear/widget/CircledImageView.java
index 03ed8c9..c441dd5 100644
--- a/android/support/wear/widget/CircledImageView.java
+++ b/android/support/wear/widget/CircledImageView.java
@@ -19,7 +19,6 @@
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
@@ -32,7 +31,6 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.Px;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
@@ -47,8 +45,7 @@
  *
  * @hide
  */
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class CircledImageView extends View {
 
     private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
@@ -133,13 +130,9 @@
         if (mDrawable != null && mDrawable.getConstantState() != null) {
             // The provided Drawable may be used elsewhere, so make a mutable clone before setTint()
             // or setAlpha() is called on it.
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                mDrawable =
-                        mDrawable.getConstantState()
-                                .newDrawable(context.getResources(), context.getTheme());
-            } else {
-                mDrawable = mDrawable.getConstantState().newDrawable(context.getResources());
-            }
+            mDrawable =
+                    mDrawable.getConstantState()
+                            .newDrawable(context.getResources(), context.getTheme());
             mDrawable = mDrawable.mutate();
         }
 
diff --git a/android/support/wear/widget/CurvingLayoutCallback.java b/android/support/wear/widget/CurvingLayoutCallback.java
index 275f1f8..5e88a8c 100644
--- a/android/support/wear/widget/CurvingLayoutCallback.java
+++ b/android/support/wear/widget/CurvingLayoutCallback.java
@@ -113,7 +113,7 @@
      */
     public void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY) {
         return;
-    };
+    }
 
     @VisibleForTesting
     void setRound(boolean isScreenRound) {
diff --git a/android/support/wear/widget/ProgressDrawable.java b/android/support/wear/widget/ProgressDrawable.java
index 08e8ec2..28e0570 100644
--- a/android/support/wear/widget/ProgressDrawable.java
+++ b/android/support/wear/widget/ProgressDrawable.java
@@ -19,14 +19,12 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
 import android.util.Property;
@@ -37,8 +35,7 @@
  *
  * @hide
  */
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class ProgressDrawable extends Drawable {
 
     private static final Property<ProgressDrawable, Integer> LEVEL =
diff --git a/android/support/wear/widget/RoundedDrawable.java b/android/support/wear/widget/RoundedDrawable.java
index fd09a87..300b6dd 100644
--- a/android/support/wear/widget/RoundedDrawable.java
+++ b/android/support/wear/widget/RoundedDrawable.java
@@ -15,7 +15,6 @@
  */
 package android.support.wear.widget;
 
-import android.annotation.TargetApi;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
@@ -29,7 +28,6 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -76,7 +74,6 @@
  *   app:radius="dimension"
  *   app:clipEnabled="boolean" /&gt;</pre>
  */
-@TargetApi(Build.VERSION_CODES.N)
 public class RoundedDrawable extends Drawable {
 
     @VisibleForTesting
diff --git a/android/support/wear/widget/ScrollManager.java b/android/support/wear/widget/ScrollManager.java
index 8155f62..e01a271 100644
--- a/android/support/wear/widget/ScrollManager.java
+++ b/android/support/wear/widget/ScrollManager.java
@@ -16,11 +16,8 @@
 
 package android.support.wear.widget;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.annotation.TargetApi;
-import android.os.Build;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
 import android.support.v7.widget.RecyclerView;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -30,8 +27,7 @@
  *
  * @hide
  */
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class ScrollManager {
     // One second in milliseconds.
     private static final int ONE_SEC_IN_MS = 1000;
diff --git a/android/support/wear/widget/SimpleAnimatorListener.java b/android/support/wear/widget/SimpleAnimatorListener.java
index a60b0bd..3a1e56b 100644
--- a/android/support/wear/widget/SimpleAnimatorListener.java
+++ b/android/support/wear/widget/SimpleAnimatorListener.java
@@ -29,7 +29,7 @@
  * @hide Hidden until this goes through review
  */
 @RequiresApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class SimpleAnimatorListener implements Animator.AnimatorListener {
 
     private boolean mWasCanceled;
diff --git a/android/support/wear/widget/SwipeDismissLayout.java b/android/support/wear/widget/SwipeDismissLayout.java
index 6e7a6f3..33da79c 100644
--- a/android/support/wear/widget/SwipeDismissLayout.java
+++ b/android/support/wear/widget/SwipeDismissLayout.java
@@ -16,12 +16,11 @@
 
 package android.support.wear.widget;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
 import android.support.annotation.UiThread;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -40,7 +39,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 @UiThread
 class SwipeDismissLayout extends FrameLayout {
     private static final String TAG = "SwipeDismissLayout";
diff --git a/android/support/wear/widget/WearableRecyclerView.java b/android/support/wear/widget/WearableRecyclerView.java
index 5cacdfc..1425e68 100644
--- a/android/support/wear/widget/WearableRecyclerView.java
+++ b/android/support/wear/widget/WearableRecyclerView.java
@@ -16,11 +16,9 @@
 
 package android.support.wear.widget;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Point;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.support.wear.R;
@@ -35,7 +33,6 @@
  *
  * @see #setCircularScrollingGestureEnabled(boolean)
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableRecyclerView extends RecyclerView {
     private static final String TAG = "WearableRecyclerView";
 
diff --git a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
index f1cb640..e9b2a40 100644
--- a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
@@ -32,7 +32,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class AbsListViewFlingWatcher implements FlingWatcher, OnScrollListener {
 
     private final FlingListener mListener;
diff --git a/android/support/wear/widget/drawer/FlingWatcherFactory.java b/android/support/wear/widget/drawer/FlingWatcherFactory.java
index 3fe84c6..2fdfa13 100644
--- a/android/support/wear/widget/drawer/FlingWatcherFactory.java
+++ b/android/support/wear/widget/drawer/FlingWatcherFactory.java
@@ -33,7 +33,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class FlingWatcherFactory {
 
     /**
diff --git a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
index ca95ab2..4c0e5c8 100644
--- a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class NestedScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
 
     static final int MAX_WAIT_TIME_MS = 100;
diff --git a/android/support/wear/widget/drawer/PageIndicatorView.java b/android/support/wear/widget/drawer/PageIndicatorView.java
index 99c7c09..1285f72 100644
--- a/android/support/wear/widget/drawer/PageIndicatorView.java
+++ b/android/support/wear/widget/drawer/PageIndicatorView.java
@@ -54,7 +54,7 @@
  * @hide
  */
 @RequiresApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class PageIndicatorView extends View implements OnPageChangeListener {
 
     private static final String TAG = "Dots";
diff --git a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
index 7570fae..7916875 100644
--- a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
@@ -31,7 +31,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class RecyclerViewFlingWatcher extends OnScrollListener implements FlingWatcher {
 
     private final FlingListener mListener;
diff --git a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
index f0b973b..5154e7b 100644
--- a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class ScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
 
     static final int MAX_WAIT_TIME_MS = 100;
diff --git a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
index 158467d..092ac72 100644
--- a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
+++ b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
@@ -16,12 +16,10 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.view.ActionProvider;
 import android.view.ContextMenu;
@@ -34,7 +32,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@TargetApi(Build.VERSION_CODES.M)
 /* package */ class WearableActionDrawerMenu implements Menu {
 
     private final Context mContext;
diff --git a/android/support/wear/widget/drawer/WearableActionDrawerView.java b/android/support/wear/widget/drawer/WearableActionDrawerView.java
index 8154e6b..99cd4ff 100644
--- a/android/support/wear/widget/drawer/WearableActionDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableActionDrawerView.java
@@ -16,12 +16,10 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -38,6 +36,7 @@
 import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -75,7 +74,6 @@
  * <p>For {@link MenuItem}, setting and getting the title and icon, {@link MenuItem#getItemId}, and
  * {@link MenuItem#setOnMenuItemClickListener} are implemented.
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableActionDrawerView extends WearableDrawerView {
 
     private static final String TAG = "WearableActionDrawer";
@@ -140,12 +138,8 @@
             View peekView = layoutInflater.inflate(R.layout.ws_action_drawer_peek_view,
                     getPeekContainer(), false /* attachToRoot */);
             setPeekContent(peekView);
-            mPeekActionIcon =
-                    (ImageView) peekView
-                            .findViewById(R.id.ws_action_drawer_peek_action_icon);
-            mPeekExpandIcon =
-                    (ImageView) peekView
-                            .findViewById(R.id.ws_action_drawer_expand_icon);
+            mPeekActionIcon = peekView.findViewById(R.id.ws_action_drawer_peek_action_icon);
+            mPeekExpandIcon = peekView.findViewById(R.id.ws_action_drawer_expand_icon);
         } else {
             mPeekActionIcon = null;
             mPeekExpandIcon = null;
@@ -193,6 +187,16 @@
     }
 
     @Override
+    public void onDrawerOpened() {
+        if (mActionListAdapter.getItemCount() > 0) {
+            RecyclerView.ViewHolder holder = mActionList.findViewHolderForAdapterPosition(0);
+            if (holder != null && holder.itemView != null) {
+                holder.itemView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+            }
+        }
+    }
+
+    @Override
     public boolean canScrollHorizontally(int direction) {
         // Prevent the window from being swiped closed while it is open by saying that it can scroll
         // horizontally.
@@ -412,7 +416,6 @@
                 CharSequence title = mActionMenu.getItem(titleAwarePosition).getTitle();
                 holder.textView.setText(title);
                 holder.textView.setContentDescription(title);
-                holder.iconView.setContentDescription(title);
                 holder.iconView.setImageDrawable(icon);
             } else if (viewHolder instanceof TitleViewHolder) {
                 TitleViewHolder holder = (TitleViewHolder) viewHolder;
diff --git a/android/support/wear/widget/drawer/WearableDrawerLayout.java b/android/support/wear/widget/drawer/WearableDrawerLayout.java
index 6d27064..e100a46 100644
--- a/android/support/wear/widget/drawer/WearableDrawerLayout.java
+++ b/android/support/wear/widget/drawer/WearableDrawerLayout.java
@@ -19,11 +19,10 @@
 import static android.support.wear.widget.drawer.WearableDrawerView.STATE_IDLE;
 import static android.support.wear.widget.drawer.WearableDrawerView.STATE_SETTLING;
 
-import android.annotation.TargetApi;
 import android.content.Context;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.view.NestedScrollingParent;
@@ -98,7 +97,6 @@
  *     &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;
  * &lt;/android.support.wear.widget.drawer.WearableDrawerLayout&gt;</pre>
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableDrawerLayout extends FrameLayout
         implements View.OnLayoutChangeListener, NestedScrollingParent, FlingListener {
 
@@ -654,12 +652,13 @@
     }
 
     @Override // NestedScrollingParent
-    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+    public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
+            boolean consumed) {
         return false;
     }
 
     @Override // NestedScrollingParent
-    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
         maybeUpdateScrollingContentView(target);
         mLastScrollWasFling = true;
 
@@ -674,13 +673,13 @@
     }
 
     @Override // NestedScrollingParent
-    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
         maybeUpdateScrollingContentView(target);
     }
 
     @Override // NestedScrollingParent
-    public void onNestedScroll(
-            View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed) {
 
         boolean scrolledUp = dyConsumed < 0;
         boolean scrolledDown = dyConsumed > 0;
@@ -873,18 +872,20 @@
     }
 
     @Override // NestedScrollingParent
-    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target,
+            int nestedScrollAxes) {
         mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
     }
 
     @Override // NestedScrollingParent
-    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target,
+            int nestedScrollAxes) {
         mCurrentNestedScrollSlopTracker = 0;
         return true;
     }
 
     @Override // NestedScrollingParent
-    public void onStopNestedScroll(View target) {
+    public void onStopNestedScroll(@NonNull View target) {
         mNestedScrollingParentHelper.onStopNestedScroll(target);
     }
 
@@ -961,7 +962,7 @@
         public abstract WearableDrawerView getDrawerView();
 
         @Override
-        public boolean tryCaptureView(View child, int pointerId) {
+        public boolean tryCaptureView(@NonNull View child, int pointerId) {
             WearableDrawerView drawerView = getDrawerView();
             // Returns true if the dragger is dragging the drawer.
             return child == drawerView && !drawerView.isLocked()
@@ -969,13 +970,13 @@
         }
 
         @Override
-        public int getViewVerticalDragRange(View child) {
+        public int getViewVerticalDragRange(@NonNull View child) {
             // Defines the vertical drag range of the drawer.
             return child == getDrawerView() ? child.getHeight() : 0;
         }
 
         @Override
-        public void onViewCaptured(View capturedChild, int activePointerId) {
+        public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
             showDrawerContentMaybeAnimate((WearableDrawerView) capturedChild);
         }
 
@@ -1036,7 +1037,7 @@
     private class TopDrawerDraggerCallback extends DrawerDraggerCallback {
 
         @Override
-        public int clampViewPositionVertical(View child, int top, int dy) {
+        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
             if (mTopDrawerView == child) {
                 int peekHeight = mTopDrawerView.getPeekContainer().getHeight();
                 // The top drawer can be dragged vertically from peekHeight - height to 0.
@@ -1063,7 +1064,7 @@
         }
 
         @Override
-        public void onViewReleased(View releasedChild, float xvel, float yvel) {
+        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
             if (releasedChild == mTopDrawerView) {
                 // Settle to final position. Either swipe open or close.
                 final float openedPercent = mTopDrawerView.getOpenedPercent();
@@ -1085,7 +1086,8 @@
         }
 
         @Override
-        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) {
             if (changedView == mTopDrawerView) {
                 // Compute the offset and invalidate will move the drawer during layout.
                 final int height = changedView.getHeight();
@@ -1106,7 +1108,7 @@
     private class BottomDrawerDraggerCallback extends DrawerDraggerCallback {
 
         @Override
-        public int clampViewPositionVertical(View child, int top, int dy) {
+        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
             if (mBottomDrawerView == child) {
                 // The bottom drawer can be dragged vertically from (parentHeight - height) to
                 // (parentHeight - peekHeight).
@@ -1131,7 +1133,7 @@
         }
 
         @Override
-        public void onViewReleased(View releasedChild, float xvel, float yvel) {
+        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
             if (releasedChild == mBottomDrawerView) {
                 // Settle to final position. Either swipe open or close.
                 final int parentHeight = getHeight();
@@ -1151,7 +1153,8 @@
         }
 
         @Override
-        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) {
             if (changedView == mBottomDrawerView) {
                 // Compute the offset and invalidate will move the drawer during layout.
                 final int height = changedView.getHeight();
diff --git a/android/support/wear/widget/drawer/WearableDrawerView.java b/android/support/wear/widget/drawer/WearableDrawerView.java
index dafac39..2462cba 100644
--- a/android/support/wear/widget/drawer/WearableDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableDrawerView.java
@@ -16,11 +16,9 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.IdRes;
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
@@ -87,7 +85,6 @@
  *     &lt;/LinearLayout&gt;
  * &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;</pre>
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableDrawerView extends FrameLayout {
     /**
      * Indicates that the drawer is in an idle, settled state. No animation is in progress.
@@ -109,7 +106,7 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(Scope.LIBRARY_GROUP)
+    @RestrictTo(Scope.LIBRARY)
     @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
     public @interface DrawerState {}
 
@@ -155,8 +152,8 @@
         setElevation(context.getResources()
                 .getDimension(R.dimen.ws_wearable_drawer_view_elevation));
 
-        mPeekContainer = (ViewGroup) findViewById(R.id.ws_drawer_view_peek_container);
-        mPeekIcon = (ImageView) findViewById(R.id.ws_drawer_view_peek_icon);
+        mPeekContainer = findViewById(R.id.ws_drawer_view_peek_container);
+        mPeekIcon = findViewById(R.id.ws_drawer_view_peek_icon);
 
         mPeekContainer.setOnClickListener(
                 new OnClickListener() {
diff --git a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
index 480812b..c5c49fe 100644
--- a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
@@ -16,11 +16,9 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.support.annotation.IntDef;
@@ -58,7 +56,6 @@
  * <p>The developer may specify which style to use with the {@code app:navigationStyle} custom
  * attribute. If not specified, {@link #SINGLE_PAGE singlePage} will be used as the default.
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableNavigationDrawerView extends WearableDrawerView {
 
     private static final String TAG = "WearableNavDrawer";
@@ -79,7 +76,7 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(Scope.LIBRARY_GROUP)
+    @RestrictTo(Scope.LIBRARY)
     @IntDef({SINGLE_PAGE, MULTI_PAGE})
     public @interface NavigationStyle {}
 
@@ -282,7 +279,7 @@
         /**
          * @hide
          */
-        @RestrictTo(Scope.LIBRARY_GROUP)
+        @RestrictTo(Scope.LIBRARY)
         public void setPresenter(WearableNavigationDrawerPresenter presenter) {
             mPresenter = presenter;
         }
diff --git a/android/support/wearable/watchface/decomposition/package-info.java b/android/support/wearable/watchface/decomposition/package-info.java
new file mode 100644
index 0000000..dbd815e
--- /dev/null
+++ b/android/support/wearable/watchface/decomposition/package-info.java
@@ -0,0 +1,2 @@
+/** @hide */
+package android.support.wearable.watchface.decomposition;
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/system/Int32Ref.java
similarity index 69%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/system/Int32Ref.java
index e28e1b3..25a818d 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/system/Int32Ref.java
@@ -11,16 +11,18 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package android.telephony.ims.feature;
+package android.system;
 
 /**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
+ * A signed 32bit integer reference suitable for passing to lower-level system calls.
  */
+public class Int32Ref {
+    public int value;
 
-public interface IRcsFeature {
+    public Int32Ref(int value) {
+        this.value = value;
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/system/Int64Ref.java
similarity index 69%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/system/Int64Ref.java
index e28e1b3..f42450d 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/system/Int64Ref.java
@@ -11,16 +11,18 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package android.telephony.ims.feature;
+package android.system;
 
 /**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
+ * A signed 64bit integer reference suitable for passing to lower-level system calls.
  */
+public class Int64Ref {
+    public long value;
 
-public interface IRcsFeature {
+    public Int64Ref(long value) {
+        this.value = value;
+    }
 }
diff --git a/android/system/Os.java b/android/system/Os.java
index 2dabae2..fe8f7d4 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -188,6 +188,17 @@
     public static int getgid() { return Libcore.os.getgid(); }
 
     /**
+     * See <a href="http://man7.org/linux/man-pages/man2/getgroups.2.html">getgroups(2)</a>.
+     *
+     * <p>Should the number of groups change during the execution of this call, the call may
+     *    return an arbitrary subset. This may be worth reconsidering should this be exposed
+     *    as public API.
+     *
+     * @hide
+     */
+    public static int[] getgroups() throws ErrnoException { return Libcore.os.getgroups(); }
+
+    /**
      * See <a href="http://man7.org/linux/man-pages/man3/getenv.3.html">getenv(3)</a>.
      */
     public static String getenv(String name) { return Libcore.os.getenv(name); }
@@ -268,7 +279,16 @@
     public static InetAddress inet_pton(int family, String address) { return Libcore.os.inet_pton(family, address); }
 
     /** @hide */ public static InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return Libcore.os.ioctlInetAddress(fd, cmd, interfaceName); }
-    /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return Libcore.os.ioctlInt(fd, cmd, arg); }
+
+
+    /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, Int32Ref arg) throws ErrnoException {
+        libcore.util.MutableInt internalArg = new libcore.util.MutableInt(arg.value);
+        try {
+            return Libcore.os.ioctlInt(fd, cmd, internalArg);
+        } finally {
+            arg.value = internalArg.value;
+        }
+    }
 
     /**
      * See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
@@ -453,8 +473,41 @@
 
     /**
      * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
+     *
+     * @deprecated This method will be removed in a future version of Android. Use
+     *        {@link #sendfile(FileDescriptor, FileDescriptor, Int64Ref, long)} instead.
      */
-    public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return Libcore.os.sendfile(outFd, inFd, inOffset, byteCount); }
+    @Deprecated
+    public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException {
+        if (inOffset == null) {
+            return Libcore.os.sendfile(outFd, inFd, null, byteCount);
+        } else {
+            libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong(
+                    inOffset.value);
+            try {
+                return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount);
+            } finally {
+                inOffset.value = internalInOffset.value;
+            }
+        }
+    }
+
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
+     */
+    public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, Int64Ref inOffset, long byteCount) throws ErrnoException {
+        if (inOffset == null) {
+            return Libcore.os.sendfile(outFd, inFd, null, byteCount);
+        } else {
+            libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong(
+                    inOffset.value);
+            try {
+                return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount);
+            } finally {
+                inOffset.value = internalInOffset.value;
+            }
+        }
+    }
 
     /**
      * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
@@ -492,6 +545,13 @@
     public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
 
     /**
+     * See <a href="http://man7.org/linux/man-pages/man2/setgroups.2.html">setgroups(2)</a>.
+     *
+     * @hide
+     */
+    public static void setgroups(int[] gids) throws ErrnoException { Libcore.os.setgroups(gids); }
+
+    /**
      * See <a href="http://man7.org/linux/man-pages/man2/setpgid.2.html">setpgid(2)</a>.
      */
     /** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }
@@ -611,8 +671,41 @@
 
     /**
      * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
+     *
+     * @deprecated This method will be removed in a future version of Android. Use
+     *        {@link #waitpid(int, Int32Ref, int)} instead.
      */
-    public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return Libcore.os.waitpid(pid, status, options); }
+    @Deprecated
+    public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException {
+        if (status == null) {
+            return Libcore.os.waitpid(pid, null, options);
+        } else {
+            libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value);
+            try {
+                return Libcore.os.waitpid(pid, internalStatus, options);
+            } finally {
+                status.value = internalStatus.value;
+            }
+        }
+    }
+
+    /**
+     * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
+     *
+     * @throws IllegalArgumentException if {@code status != null && status.length != 1}
+     */
+    public static int waitpid(int pid, Int32Ref status, int options) throws ErrnoException {
+        if (status == null) {
+            return Libcore.os.waitpid(pid, null, options);
+        } else {
+            libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value);
+            try {
+                return Libcore.os.waitpid(pid, internalStatus, options);
+            } finally {
+                status.value = internalStatus.value;
+            }
+        }
+    }
 
     /**
      * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
diff --git a/android/system/UnixSocketAddressTest.java b/android/system/UnixSocketAddressTest.java
deleted file mode 100644
index f1b7fc2..0000000
--- a/android/system/UnixSocketAddressTest.java
+++ /dev/null
@@ -1,51 +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.system;
-
-import junit.framework.TestCase;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-public class UnixSocketAddressTest extends TestCase {
-
-    public void testFilesystemSunPath() throws Exception {
-        String path = "/foo/bar";
-        UnixSocketAddress sa = UnixSocketAddress.createFileSystem(path);
-
-        byte[] abstractNameBytes = path.getBytes(StandardCharsets.UTF_8);
-        byte[] expected = new byte[abstractNameBytes.length + 1];
-        // See unix(7)
-        System.arraycopy(abstractNameBytes, 0, expected, 0, abstractNameBytes.length);
-        assertTrue(Arrays.equals(expected, sa.getSunPath()));
-    }
-
-    public void testUnnamedSunPath() throws Exception {
-        UnixSocketAddress sa = UnixSocketAddress.createUnnamed();
-        assertEquals(0, sa.getSunPath().length);
-    }
-
-    public void testAbstractSunPath() throws Exception {
-        String abstractName = "abstract";
-        UnixSocketAddress sa = UnixSocketAddress.createAbstract(abstractName);
-        byte[] abstractNameBytes = abstractName.getBytes(StandardCharsets.UTF_8);
-        byte[] expected = new byte[abstractNameBytes.length + 1];
-        // See unix(7)
-        System.arraycopy(abstractNameBytes, 0, expected, 1, abstractNameBytes.length);
-        assertTrue(Arrays.equals(expected, sa.getSunPath()));
-    }
-}
diff --git a/android/telecom/Call.java b/android/telecom/Call.java
index e13bd61..a07f2bb 100644
--- a/android/telecom/Call.java
+++ b/android/telecom/Call.java
@@ -855,6 +855,39 @@
      */
     public static abstract class Callback {
         /**
+         * @hide
+         */
+        @IntDef({HANDOVER_FAILURE_DEST_APP_REJECTED, HANDOVER_FAILURE_DEST_NOT_SUPPORTED,
+                HANDOVER_FAILURE_DEST_INVALID_PERM, HANDOVER_FAILURE_DEST_USER_REJECTED})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface HandoverFailureErrors {}
+
+        /**
+         * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when the app
+         * to handover the call rejects handover.
+         */
+        public static final int HANDOVER_FAILURE_DEST_APP_REJECTED = 1;
+
+        /**
+         * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when there is
+         * an error associated with unsupported handover.
+         */
+        public static final int HANDOVER_FAILURE_DEST_NOT_SUPPORTED = 2;
+
+        /**
+         * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when there
+         * are some permission errors associated with APIs doing handover.
+         */
+        public static final int HANDOVER_FAILURE_DEST_INVALID_PERM = 3;
+
+        /**
+         * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when user
+         * rejects handover.
+         */
+        public static final int HANDOVER_FAILURE_DEST_USER_REJECTED = 4;
+
+
+        /**
          * Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
          *
          * @param call The {@code Call} invoking this method.
@@ -989,6 +1022,21 @@
          *               {@link android.telecom.Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
          */
         public void onRttInitiationFailure(Call call, int reason) {}
+
+        /**
+         * Invoked when Call handover from one {@link PhoneAccount} to other {@link PhoneAccount}
+         * has completed successfully.
+         * @param call The call which had initiated handover.
+         */
+        public void onHandoverComplete(Call call) {}
+
+        /**
+         * Invoked when Call handover from one {@link PhoneAccount} to other {@link PhoneAccount}
+         * has failed.
+         * @param call The call which had initiated handover.
+         * @param failureReason Error reason for failure
+         */
+        public void onHandoverFailed(Call call, @HandoverFailureErrors int failureReason) {}
     }
 
     /**
@@ -1367,6 +1415,24 @@
     }
 
     /**
+     * Initiates a handover of this {@link Call} to the {@link ConnectionService} identified
+     * by {@code toHandle}.  The videoState specified indicates the desired video state after the
+     * handover.
+     * <p>
+     * A handover request is initiated by the user from one app to indicate a desire
+     * to handover a call to another.
+     *
+     * @param toHandle {@link PhoneAccountHandle} of the {@link ConnectionService} to handover
+     *                 this call to.
+     * @param videoState Indicates the video state desired after the handover.
+     * @param extras Bundle containing extra information to be passed to the
+     *               {@link ConnectionService}
+     */
+    public void handoverTo(PhoneAccountHandle toHandle, int videoState, Bundle extras) {
+        mInCallAdapter.handoverTo(mTelecomCallId, toHandle, videoState, extras);
+    }
+
+    /**
      * Terminate the RTT session on this call. The resulting state change will be notified via
      * the {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback.
      */
diff --git a/android/telecom/CallAudioState.java b/android/telecom/CallAudioState.java
index f601d8b..4b827d2 100644
--- a/android/telecom/CallAudioState.java
+++ b/android/telecom/CallAudioState.java
@@ -16,16 +16,35 @@
 
 package android.telecom;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  *  Encapsulates the telecom audio state, including the current audio routing, supported audio
  *  routing and mute.
  */
 public final class CallAudioState implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
+            flag=true)
+    public @interface CallAudioRoute {}
+
     /** Direct the audio stream through the device's earpiece. */
     public static final int ROUTE_EARPIECE      = 0x00000001;
 
@@ -55,6 +74,8 @@
     private final boolean isMuted;
     private final int route;
     private final int supportedRouteMask;
+    private final BluetoothDevice activeBluetoothDevice;
+    private final Collection<BluetoothDevice> supportedBluetoothDevices;
 
     /**
      * Constructor for a {@link CallAudioState} object.
@@ -73,10 +94,21 @@
      * {@link #ROUTE_WIRED_HEADSET}
      * {@link #ROUTE_SPEAKER}
      */
-    public CallAudioState(boolean muted, int route, int supportedRouteMask) {
-        this.isMuted = muted;
+    public CallAudioState(boolean muted, @CallAudioRoute int route,
+            @CallAudioRoute int supportedRouteMask) {
+        this(muted, route, supportedRouteMask, null, Collections.emptyList());
+    }
+
+    /** @hide */
+    public CallAudioState(boolean isMuted, @CallAudioRoute int route,
+            @CallAudioRoute int supportedRouteMask,
+            @Nullable BluetoothDevice activeBluetoothDevice,
+            @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) {
+        this.isMuted = isMuted;
         this.route = route;
         this.supportedRouteMask = supportedRouteMask;
+        this.activeBluetoothDevice = activeBluetoothDevice;
+        this.supportedBluetoothDevices = supportedBluetoothDevices;
     }
 
     /** @hide */
@@ -84,6 +116,8 @@
         isMuted = state.isMuted();
         route = state.getRoute();
         supportedRouteMask = state.getSupportedRouteMask();
+        activeBluetoothDevice = state.activeBluetoothDevice;
+        supportedBluetoothDevices = state.getSupportedBluetoothDevices();
     }
 
     /** @hide */
@@ -92,6 +126,8 @@
         isMuted = state.isMuted();
         route = state.getRoute();
         supportedRouteMask = state.getSupportedRouteMask();
+        activeBluetoothDevice = null;
+        supportedBluetoothDevices = Collections.emptyList();
     }
 
     @Override
@@ -103,17 +139,32 @@
             return false;
         }
         CallAudioState state = (CallAudioState) obj;
-        return isMuted() == state.isMuted() && getRoute() == state.getRoute() &&
-                getSupportedRouteMask() == state.getSupportedRouteMask();
+        if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) {
+            return false;
+        }
+        for (BluetoothDevice device : supportedBluetoothDevices) {
+            if (!state.supportedBluetoothDevices.contains(device)) {
+                return false;
+            }
+        }
+        return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() ==
+                state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() ==
+                state.getSupportedRouteMask();
     }
 
     @Override
     public String toString() {
+        String bluetoothDeviceList = supportedBluetoothDevices.stream()
+                .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+
         return String.format(Locale.US,
-                "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]",
+                "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
+                        "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]",
                 isMuted,
                 audioRouteToString(route),
-                audioRouteToString(supportedRouteMask));
+                audioRouteToString(supportedRouteMask),
+                activeBluetoothDevice,
+                bluetoothDeviceList);
     }
 
     /**
@@ -126,6 +177,7 @@
     /**
      * @return The current audio route being used.
      */
+    @CallAudioRoute
     public int getRoute() {
         return route;
     }
@@ -133,11 +185,27 @@
     /**
      * @return Bit mask of all routes supported by this call.
      */
+    @CallAudioRoute
     public int getSupportedRouteMask() {
         return supportedRouteMask;
     }
 
     /**
+     * @return The {@link BluetoothDevice} through which audio is being routed.
+     *         Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}.
+     */
+    public BluetoothDevice getActiveBluetoothDevice() {
+        return activeBluetoothDevice;
+    }
+
+    /**
+     * @return {@link List} of {@link BluetoothDevice}s that can be used for this call.
+     */
+    public Collection<BluetoothDevice> getSupportedBluetoothDevices() {
+        return supportedBluetoothDevices;
+    }
+
+    /**
      * Converts the provided audio route into a human readable string representation.
      *
      * @param route to convert into a string.
@@ -177,7 +245,13 @@
             boolean isMuted = source.readByte() == 0 ? false : true;
             int route = source.readInt();
             int supportedRouteMask = source.readInt();
-            return new CallAudioState(isMuted, route, supportedRouteMask);
+            BluetoothDevice activeBluetoothDevice = source.readParcelable(
+                    ClassLoader.getSystemClassLoader());
+            List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
+            source.readParcelableList(supportedBluetoothDevices,
+                    ClassLoader.getSystemClassLoader());
+            return new CallAudioState(isMuted, route,
+                    supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
         }
 
         @Override
@@ -202,6 +276,8 @@
         destination.writeByte((byte) (isMuted ? 1 : 0));
         destination.writeInt(route);
         destination.writeInt(supportedRouteMask);
+        destination.writeParcelable(activeBluetoothDevice, 0);
+        destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0);
     }
 
     private static void listAppend(StringBuffer buffer, String str) {
diff --git a/android/telecom/Connection.java b/android/telecom/Connection.java
index 8ba934c..2bb1c4e 100644
--- a/android/telecom/Connection.java
+++ b/android/telecom/Connection.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.Notification;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
@@ -819,7 +820,7 @@
         public void onConnectionEvent(Connection c, String event, Bundle extras) {}
         /** @hide */
         public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
-        public void onAudioRouteChanged(Connection c, int audioRoute) {}
+        public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {}
         public void onRttInitiationSuccess(Connection c) {}
         public void onRttInitiationFailure(Connection c, int reason) {}
         public void onRttSessionRemotelyTerminated(Connection c) {}
@@ -1683,6 +1684,8 @@
 
     // The internal telecom call ID associated with this connection.
     private String mTelecomCallId;
+    // The PhoneAccountHandle associated with this connection.
+    private PhoneAccountHandle mPhoneAccountHandle;
     private int mState = STATE_NEW;
     private CallAudioState mCallAudioState;
     private Uri mAddress;
@@ -2576,7 +2579,29 @@
      */
     public final void setAudioRoute(int route) {
         for (Listener l : mListeners) {
-            l.onAudioRouteChanged(this, route);
+            l.onAudioRouteChanged(this, route, null);
+        }
+    }
+
+    /**
+     *
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified if the
+     * bluetooth stack is unable to route audio to the requested device.
+     * A list of available devices can be obtained via
+     * {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * <p>
+     * Used by self-managed {@link ConnectionService}s which wish to use bluetooth audio for a
+     * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+     * <p>
+     * See also {@link InCallService#requestBluetoothAudio(String)}
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     *                         {@link BluetoothDevice#getAddress()}.
+     */
+    public void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+        for (Listener l : mListeners) {
+            l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
         }
     }
 
@@ -3076,6 +3101,27 @@
     }
 
     /**
+     * Sets the {@link PhoneAccountHandle} associated with this connection.
+     *
+     * @hide
+     */
+    public void setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
+        if (mPhoneAccountHandle != phoneAccountHandle) {
+            mPhoneAccountHandle = phoneAccountHandle;
+            notifyPhoneAccountChanged(phoneAccountHandle);
+        }
+    }
+
+    /**
+     * Returns the {@link PhoneAccountHandle} associated with this connection.
+     *
+     * @hide
+     */
+    public PhoneAccountHandle getPhoneAccountHandle() {
+        return mPhoneAccountHandle;
+    }
+
+    /**
      * Sends an event associated with this {@code Connection} with associated event extras to the
      * {@link InCallService}.
      * <p>
diff --git a/android/telecom/ConnectionService.java b/android/telecom/ConnectionService.java
index a81fba9..7e83306 100644
--- a/android/telecom/ConnectionService.java
+++ b/android/telecom/ConnectionService.java
@@ -1294,10 +1294,10 @@
         }
 
         @Override
-        public void onAudioRouteChanged(Connection c, int audioRoute) {
+        public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
             String id = mIdByConnection.get(c);
             if (id != null) {
-                mAdapter.setAudioRoute(id, audioRoute);
+                mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
             }
         }
 
@@ -1382,7 +1382,7 @@
 
         connection.setTelecomCallId(callId);
         if (connection.getState() != Connection.STATE_DISCONNECTED) {
-            addConnection(callId, connection);
+            addConnection(request.getAccountHandle(), callId, connection);
         }
 
         Uri address = connection.getAddress();
@@ -1846,6 +1846,7 @@
                     mAdapter.setIsConferenced(connectionId, id);
                 }
             }
+            onConferenceAdded(conference);
         }
     }
 
@@ -2033,6 +2034,43 @@
     }
 
     /**
+     * Called by Telecom on the initiating side of the handover to create an instance of a
+     * handover connection.
+     * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
+     *                               ConnectionService which needs to handover the call.
+     * @param request Details about the call which needs to be handover.
+     * @return Connection object corresponding to the handover call.
+     */
+    public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+                                                         ConnectionRequest request) {
+        return null;
+    }
+
+    /**
+     * Called by Telecom on the receiving side of the handover to request the
+     * {@link ConnectionService} to create an instance of a handover connection.
+     * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
+     *                               ConnectionService which needs to handover the call.
+     * @param request Details about the call which needs to be handover.
+     * @return {@link Connection} object corresponding to the handover call.
+     */
+    public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+                                                         ConnectionRequest request) {
+        return null;
+    }
+
+    /**
+     * Called by Telecom in response to a {@code TelecomManager#acceptHandover()}
+     * invocation which failed.
+     * @param request Details about the call which needs to be handover.
+     * @param error Reason for handover failure as defined in
+     *              {@link android.telecom.Call.Callback#HANDOVER_FAILURE_DEST_INVALID_PERM}
+     */
+    public void onHandoverFailed(ConnectionRequest request, int error) {
+        return;
+    }
+
+    /**
      * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
      * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
      * call created using
@@ -2056,6 +2094,30 @@
     public void onConference(Connection connection1, Connection connection2) {}
 
     /**
+     * Called when a connection is added.
+     * @hide
+     */
+    public void onConnectionAdded(Connection connection) {}
+
+    /**
+     * Called when a connection is removed.
+     * @hide
+     */
+    public void onConnectionRemoved(Connection connection) {}
+
+    /**
+     * Called when a conference is added.
+     * @hide
+     */
+    public void onConferenceAdded(Conference conference) {}
+
+    /**
+     * Called when a conference is removed.
+     * @hide
+     */
+    public void onConferenceRemoved(Conference conference) {}
+
+    /**
      * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
      * When this method is invoked, this {@link ConnectionService} should create its own
      * representation of the conference call and send it to telecom using {@link #addConference}.
@@ -2122,16 +2184,18 @@
             // prefix for a unique incremental call ID.
             id = handle.getComponentName().getClassName() + "@" + getNextCallId();
         }
-        addConnection(id, connection);
+        addConnection(handle, id, connection);
         return id;
     }
 
-    private void addConnection(String callId, Connection connection) {
+    private void addConnection(PhoneAccountHandle handle, String callId, Connection connection) {
         connection.setTelecomCallId(callId);
         mConnectionById.put(callId, connection);
         mIdByConnection.put(connection, callId);
         connection.addConnectionListener(mConnectionListener);
         connection.setConnectionService(this);
+        connection.setPhoneAccountHandle(handle);
+        onConnectionAdded(connection);
     }
 
     /** {@hide} */
@@ -2143,6 +2207,7 @@
             mConnectionById.remove(id);
             mIdByConnection.remove(connection);
             mAdapter.removeCall(id);
+            onConnectionRemoved(connection);
         }
     }
 
@@ -2179,6 +2244,8 @@
             mConferenceById.remove(id);
             mIdByConference.remove(conference);
             mAdapter.removeCall(id);
+
+            onConferenceRemoved(conference);
         }
     }
 
diff --git a/android/telecom/ConnectionServiceAdapter.java b/android/telecom/ConnectionServiceAdapter.java
index 111fcc7..92a9dc2 100644
--- a/android/telecom/ConnectionServiceAdapter.java
+++ b/android/telecom/ConnectionServiceAdapter.java
@@ -520,11 +520,14 @@
      * @param callId The unique ID of the call.
      * @param audioRoute The new audio route (see {@code CallAudioState#ROUTE_*}).
      */
-    void setAudioRoute(String callId, int audioRoute) {
-        Log.v(this, "setAudioRoute: %s %s", callId, CallAudioState.audioRouteToString(audioRoute));
+    void setAudioRoute(String callId, int audioRoute, String bluetoothAddress) {
+        Log.v(this, "setAudioRoute: %s %s %s", callId,
+                CallAudioState.audioRouteToString(audioRoute),
+                bluetoothAddress);
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.setAudioRoute(callId, audioRoute, Log.getExternalSession());
+                adapter.setAudioRoute(callId, audioRoute,
+                        bluetoothAddress, Log.getExternalSession());
             } catch (RemoteException ignored) {
             }
         }
diff --git a/android/telecom/ConnectionServiceAdapterServant.java b/android/telecom/ConnectionServiceAdapterServant.java
index b1617f4..3fbdeb1 100644
--- a/android/telecom/ConnectionServiceAdapterServant.java
+++ b/android/telecom/ConnectionServiceAdapterServant.java
@@ -298,8 +298,8 @@
                 case MSG_SET_AUDIO_ROUTE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        mDelegate.setAudioRoute((String) args.arg1, args.argi1,
-                                (Session.Info) args.arg2);
+                        mDelegate.setAudioRoute((String) args.arg1, args.argi1, (String) args.arg2,
+                                (Session.Info) args.arg3);
                     } finally {
                         args.recycle();
                     }
@@ -548,12 +548,12 @@
 
         @Override
         public final void setAudioRoute(String connectionId, int audioRoute,
-                Session.Info sessionInfo) {
-
+                String bluetoothAddress, Session.Info sessionInfo) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = connectionId;
             args.argi1 = audioRoute;
-            args.arg2 = sessionInfo;
+            args.arg2 = bluetoothAddress;
+            args.arg3 = sessionInfo;
             mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, args).sendToTarget();
         }
 
diff --git a/android/telecom/InCallAdapter.java b/android/telecom/InCallAdapter.java
index 9559a28..4bc2a9b 100644
--- a/android/telecom/InCallAdapter.java
+++ b/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
 import android.os.RemoteException;
 
@@ -128,7 +129,22 @@
      */
     public void setAudioRoute(int route) {
         try {
-            mAdapter.setAudioRoute(route);
+            mAdapter.setAudioRoute(route, null);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified. A list of
+     * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+     */
+    public void requestBluetoothAudio(String bluetoothAddress) {
+        try {
+            mAdapter.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
         } catch (RemoteException e) {
         }
     }
@@ -419,4 +435,21 @@
         } catch (RemoteException ignored) {
         }
     }
+
+
+    /**
+     * Initiates a handover of this {@link Call} to the {@link ConnectionService} identified
+     * by destAcct.
+     * @param callId The callId of the Call which calls this function.
+     * @param destAcct ConnectionService to which the call should be handed over.
+     * @param videoState The video state desired after the handover.
+     * @param extras Extra information to be passed to ConnectionService
+     */
+    public void handoverTo(String callId, PhoneAccountHandle destAcct, int videoState,
+                           Bundle extras) {
+        try {
+            mAdapter.handoverTo(callId, destAcct, videoState, extras);
+        } catch (RemoteException ignored) {
+        }
+    }
 }
diff --git a/android/telecom/InCallService.java b/android/telecom/InCallService.java
index e384d46..d558bba 100644
--- a/android/telecom/InCallService.java
+++ b/android/telecom/InCallService.java
@@ -16,9 +16,11 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.Service;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
@@ -377,6 +379,22 @@
     }
 
     /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified if the
+     * bluetooth stack is unable to route audio to the requested device.
+     * A list of available devices can be obtained via
+     * {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     *                         {@link BluetoothDevice#getAddress()}.
+     */
+    public final void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+        if (mPhone != null) {
+            mPhone.requestBluetoothAudio(bluetoothAddress);
+        }
+    }
+
+    /**
      * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
      * to start displaying in-call information to the user. Each instance of {@code InCallService}
      * will have only one {@code Phone}, and this method will be called exactly once in the lifetime
diff --git a/android/telecom/Phone.java b/android/telecom/Phone.java
index 066f6c2..421b1a4 100644
--- a/android/telecom/Phone.java
+++ b/android/telecom/Phone.java
@@ -17,7 +17,9 @@
 package android.telecom;
 
 import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.util.ArrayMap;
 
 import java.util.Collections;
@@ -295,6 +297,18 @@
     }
 
     /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified. A list of
+     * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+     */
+    public void requestBluetoothAudio(String bluetoothAddress) {
+        mInCallAdapter.requestBluetoothAudio(bluetoothAddress);
+    }
+
+    /**
      * Turns the proximity sensor on. When this request is made, the proximity sensor will
      * become active, and the touch screen and display will be turned off when the user's face
      * is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/android/telecom/PhoneAccount.java b/android/telecom/PhoneAccount.java
index 691e7cf..74b9465 100644
--- a/android/telecom/PhoneAccount.java
+++ b/android/telecom/PhoneAccount.java
@@ -86,13 +86,11 @@
     /**
      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
      * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
-     * connection (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) to this
-     * {@link PhoneAccount} from a {@link PhoneAccount} specifying
-     * {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
+     * connection (see {@code android.telecom.Call#handoverTo()}) to this {@link PhoneAccount} from
+     * a {@link PhoneAccount} specifying {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
      * <p>
      * A handover request is initiated by the user from the default dialer app to indicate a desire
      * to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
-     * @hide
      */
     public static final String EXTRA_SUPPORTS_HANDOVER_TO =
             "android.telecom.extra.SUPPORTS_HANDOVER_TO";
@@ -113,12 +111,11 @@
      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
      * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
      * connection from this {@link PhoneAccount} to another {@link PhoneAccount}.
-     * (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) which specifies
+     * (see {@code android.telecom.Call#handoverTo()}) which specifies
      * {@link #EXTRA_SUPPORTS_HANDOVER_TO}.
      * <p>
      * A handover request is initiated by the user from the default dialer app to indicate a desire
      * to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
-     * @hide
      */
     public static final String EXTRA_SUPPORTS_HANDOVER_FROM =
             "android.telecom.extra.SUPPORTS_HANDOVER_FROM";
@@ -132,7 +129,6 @@
      * <p>
      * By default, Self-Managed {@link PhoneAccount}s do not log their calls to the call log.
      * Setting this extra to {@code true} provides a means for them to log their calls.
-     * @hide
      */
     public static final String EXTRA_LOG_SELF_MANAGED_CALLS =
             "android.telecom.extra.LOG_SELF_MANAGED_CALLS";
diff --git a/android/telecom/RemoteConnectionService.java b/android/telecom/RemoteConnectionService.java
index 2cc4314..85906ad 100644
--- a/android/telecom/RemoteConnectionService.java
+++ b/android/telecom/RemoteConnectionService.java
@@ -398,7 +398,8 @@
         }
 
         @Override
-        public void setAudioRoute(String callId, int audioRoute, Session.Info sessionInfo) {
+        public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+                Session.Info sessionInfo) {
             if (hasConnection(callId)) {
                 // TODO(3pcalls): handle this for remote connections.
                 // Likely we don't want to do anything since it doesn't make sense for self-managed
diff --git a/android/telecom/TelecomManager.java b/android/telecom/TelecomManager.java
index 9e52c71..da32e0b 100644
--- a/android/telecom/TelecomManager.java
+++ b/android/telecom/TelecomManager.java
@@ -1750,6 +1750,41 @@
         return false;
     }
 
+    /**
+     * Called from the recipient side of a handover to indicate a desire to accept the handover
+     * of an ongoing call to another {@link ConnectionService} identified by
+     * {@link PhoneAccountHandle} destAcct. For managed {@link ConnectionService}s, the specified
+     * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and
+     * the user must have enabled the corresponding {@link PhoneAccount}.  This can be checked using
+     * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to handover a call to it.
+     * <p>
+     * Once invoked, this method will cause the system to bind to the {@link ConnectionService}
+     * associated with the {@link PhoneAccountHandle} destAcct and call
+     * (See {@link ConnectionService#onCreateIncomingHandoverConnection}).
+     * <p>
+     * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either
+     * the {@link PhoneAccountHandle} destAcct does not correspond to a registered
+     * {@link PhoneAccount} or the associated {@link PhoneAccount} is not currently enabled by the
+     * user.
+     * <p>
+     * For a self-managed {@link ConnectionService}, a {@link SecurityException} will be thrown if
+     * the calling app does not have {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+     *
+     * @param srcAddr The {@link android.net.Uri} of the ongoing call to handover to the caller’s
+     *                {@link ConnectionService}.
+     * @param videoState Video state after the handover.
+     * @param destAcct The {@link PhoneAccountHandle} registered to the calling package.
+     */
+    public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().acceptHandover(srcAddr, videoState, destAcct);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException acceptHandover: " + e);
+        }
+    }
 
     private ITelecomService getTelecomService() {
         if (mTelecomServiceOverride != null) {
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 6fc7d23..1db6ef7 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -352,6 +352,17 @@
     public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
 
     /**
+     * Flag that specifies to use the user's own phone number as the voicemail number when there is
+     * no pre-loaded voicemail number on the SIM card.
+     * <p>
+     * {@link #KEY_DEFAULT_VM_NUMBER_STRING} takes precedence over this flag.
+     * <p>
+     * If false, the system default (*86) will be used instead.
+     */
+    public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL =
+            "config_telephony_use_own_number_for_voicemail_bool";
+
+    /**
      * When {@code true}, changes to the mobile data enabled switch will not cause the VT
      * registration state to change.  That is, turning on or off mobile data will not cause VT to be
      * enabled or disabled.
@@ -844,6 +855,14 @@
     public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
 
     /**
+     * Default Enhanced 4G LTE mode enabled. When this is {@code true}, Enhanced 4G LTE mode by
+     * default is on, otherwise if {@code false}, Enhanced 4G LTE mode by default is off.
+     * @hide
+     */
+    public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL =
+            "enhanced_4g_lte_on_by_default_bool";
+
+    /**
      * Determine whether IMS apn can be shown.
      */
     public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
@@ -1187,24 +1206,21 @@
      */
     public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
 
-
     /**
-     * @hide
-     * The default value for preferred CDMA roaming mode (aka CDMA system select.)
-     *          CDMA_ROAMING_MODE_RADIO_DEFAULT = the default roaming mode from the radio
-     *          CDMA_ROAMING_MODE_HOME = Home Networks
-     *          CDMA_ROAMING_MODE_AFFILIATED = Roaming on Affiliated networks
-     *          CDMA_ROAMING_MODE_ANY = Roaming on any networks
+     * The CDMA roaming mode (aka CDMA system select).
+     *
+     * <p>The value should be one of the CDMA_ROAMING_MODE_ constants in {@link TelephonyManager}.
+     * Values other than {@link TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT} (which is the
+     * default) will take precedence over user selection.
+     *
+     * @see TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT
+     * @see TelephonyManager#CDMA_ROAMING_MODE_HOME
+     * @see TelephonyManager#CDMA_ROAMING_MODE_AFFILIATED
+     * @see TelephonyManager#CDMA_ROAMING_MODE_ANY
      */
     public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_HOME = 0;
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_ANY = 2;
+
+
     /**
      * Boolean indicating if support is provided for directly dialing FDN number from FDN list.
      * If false, this feature is not supported.
@@ -1535,6 +1551,13 @@
             "boosted_lte_earfcns_string_array";
 
     /**
+     * Determine whether to use only RSRP for the number of LTE signal bars.
+     * @hide
+     */
+    public static final String KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL =
+            "use_only_rsrp_for_lte_signal_bar_bool";
+
+    /**
      * Key identifying if voice call barring notification is required to be shown to the user.
      * @hide
      */
@@ -1628,6 +1651,33 @@
     public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
             "feature_access_codes_string_array";
 
+    /**
+     * Determines if the carrier wants to identify high definition calls in the call log.
+     * @hide
+     */
+    public static final String KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL =
+            "identify_high_definition_calls_in_call_log_bool";
+
+    /**
+     * Flag specifying whether to use the {@link ServiceState} roaming status, which can be
+     * affected by other carrier configs (e.g.
+     * {@link #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY}), when setting the SPN display.
+     * <p>
+     * If {@code true}, the SPN display uses {@link ServiceState#getRoaming}.
+     * If {@code false} the SPN display checks if the current MCC/MNC is different from the
+     * SIM card's MCC/MNC.
+     *
+     * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+     * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
+     * @see KEY_FORCE_HOME_NETWORK_BOOL
+     *
+     * @hide
+     */
+    public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL =
+            "spn_display_rule_use_roaming_from_service_state_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1645,6 +1695,7 @@
         sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
         sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
+        sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
         sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
         sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
@@ -1769,6 +1820,7 @@
         sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
         sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
         sDefaults.putBoolean(KEY_HIDE_ENHANCED_4G_LTE_BOOL, false);
+        sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL, true);
         sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
         sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL, false);
@@ -1821,7 +1873,8 @@
         sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
         sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
         sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
-        sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT);
+        sDefaults.putInt(
+                KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
         sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, "");
 
         // Carrier Signalling Receivers
@@ -1892,6 +1945,7 @@
                 null);
         sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
         sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
+        sDefaults.putBoolean(KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
         sDefaults.putInt(IMSI_KEY_AVAILABILITY_INT, 0);
         sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
@@ -1902,6 +1956,8 @@
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
         sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
+        sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
+        sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
     }
 
     /**
diff --git a/android/telephony/PhoneStateListener.java b/android/telephony/PhoneStateListener.java
index afff6d5..9ccfa94 100644
--- a/android/telephony/PhoneStateListener.java
+++ b/android/telephony/PhoneStateListener.java
@@ -204,16 +204,6 @@
     public static final int LISTEN_VOLTE_STATE                              = 0x00004000;
 
     /**
-     * Listen for OEM hook raw event
-     *
-     * @see #onOemHookRawEvent
-     * @hide
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @Deprecated
-    public static final int LISTEN_OEM_HOOK_RAW_EVENT                       = 0x00008000;
-
-    /**
      * Listen for carrier network changes indicated by a carrier app.
      *
      * @see #onCarrierNetworkRequest
@@ -359,9 +349,6 @@
                     case LISTEN_DATA_ACTIVATION_STATE:
                         PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
                         break;
-                    case LISTEN_OEM_HOOK_RAW_EVENT:
-                        PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
-                        break;
                     case LISTEN_CARRIER_NETWORK_CHANGE:
                         PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
                         break;
@@ -556,16 +543,6 @@
     }
 
     /**
-     * Callback invoked when OEM hook raw event is received. Requires
-     * the READ_PRIVILEGED_PHONE_STATE permission.
-     * @param rawData is the byte array of the OEM hook raw data.
-     * @hide
-     */
-    public void onOemHookRawEvent(byte[] rawData) {
-        // default implementation empty
-    }
-
-    /**
      * Callback invoked when telephony has received notice from a carrier
      * app that a network action that could result in connectivity loss
      * has been requested by an app using
@@ -677,10 +654,6 @@
             send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
         }
 
-        public void onOemHookRawEvent(byte[] rawData) {
-            send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
-        }
-
         public void onCarrierNetworkChange(boolean active) {
             send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
         }
diff --git a/android/telephony/SignalStrength.java b/android/telephony/SignalStrength.java
index c8b4776..de02de7 100644
--- a/android/telephony/SignalStrength.java
+++ b/android/telephony/SignalStrength.java
@@ -68,6 +68,7 @@
     private int mTdScdmaRscp;
 
     private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
+    private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
 
     /**
      * Create a new SignalStrength from a intent notifier Bundle
@@ -108,6 +109,7 @@
         mLteRsrpBoost = 0;
         mTdScdmaRscp = INVALID;
         isGsm = true;
+        mUseOnlyRsrpForLteLevel = false;
     }
 
     /**
@@ -134,6 +136,7 @@
         mLteRsrpBoost = 0;
         mTdScdmaRscp = INVALID;
         isGsm = gsmFlag;
+        mUseOnlyRsrpForLteLevel = false;
     }
 
     /**
@@ -145,10 +148,10 @@
             int cdmaDbm, int cdmaEcio,
             int evdoDbm, int evdoEcio, int evdoSnr,
             int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
-            int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) {
+            int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag, boolean lteLevelBaseOnRsrp) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag, lteLevelBaseOnRsrp);
         mTdScdmaRscp = tdScdmaRscp;
     }
 
@@ -164,7 +167,7 @@
             int tdScdmaRscp, boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
         mTdScdmaRscp = tdScdmaRscp;
     }
 
@@ -180,7 +183,7 @@
             boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
     }
 
     /**
@@ -194,7 +197,7 @@
             boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
-                INVALID, INVALID, INVALID, 0, gsmFlag);
+                INVALID, INVALID, INVALID, 0, gsmFlag, false);
     }
 
     /**
@@ -228,7 +231,7 @@
             boolean gsm) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
-                INVALID, INVALID, INVALID, 0, gsm);
+                INVALID, INVALID, INVALID, 0, gsm, false);
     }
 
     /**
@@ -248,6 +251,7 @@
      * @param lteCqi
      * @param lteRsrpBoost
      * @param gsm
+     * @param useOnlyRsrpForLteLevel
      *
      * @hide
      */
@@ -255,7 +259,7 @@
             int cdmaDbm, int cdmaEcio,
             int evdoDbm, int evdoEcio, int evdoSnr,
             int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
-            int lteRsrpBoost, boolean gsm) {
+            int lteRsrpBoost, boolean gsm, boolean useOnlyRsrpForLteLevel) {
         mGsmSignalStrength = gsmSignalStrength;
         mGsmBitErrorRate = gsmBitErrorRate;
         mCdmaDbm = cdmaDbm;
@@ -271,6 +275,7 @@
         mLteRsrpBoost = lteRsrpBoost;
         mTdScdmaRscp = INVALID;
         isGsm = gsm;
+        mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
         if (DBG) log("initialize: " + toString());
     }
 
@@ -293,6 +298,7 @@
         mLteRsrpBoost = s.mLteRsrpBoost;
         mTdScdmaRscp = s.mTdScdmaRscp;
         isGsm = s.isGsm;
+        mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
     }
 
     /**
@@ -318,6 +324,7 @@
         mLteRsrpBoost = in.readInt();
         mTdScdmaRscp = in.readInt();
         isGsm = (in.readInt() != 0);
+        mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
     }
 
     /**
@@ -366,6 +373,7 @@
         out.writeInt(mLteRsrpBoost);
         out.writeInt(mTdScdmaRscp);
         out.writeInt(isGsm ? 1 : 0);
+        out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
     }
 
     /**
@@ -449,6 +457,17 @@
     }
 
     /**
+     * @param useOnlyRsrpForLteLevel true if it uses only RSRP for the number of LTE signal bar,
+     * otherwise false.
+     *
+     * Used by phone to use only RSRP or not for the number of LTE signal bar.
+     * @hide
+     */
+    public void setUseOnlyRsrpForLteLevel(boolean useOnlyRsrpForLteLevel) {
+        mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
+    }
+
+    /**
      * @param lteRsrpBoost - signal strength offset
      *
      * Used by phone to set the lte signal strength offset which will be
@@ -835,6 +854,13 @@
             }
         }
 
+        if (useOnlyRsrpForLteLevel()) {
+            log("getLTELevel - rsrp = " + rsrpIconLevel);
+            if (rsrpIconLevel != -1) {
+                return rsrpIconLevel;
+            }
+        }
+
         /*
          * Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5
          * dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars
@@ -915,6 +941,15 @@
     }
 
     /**
+     * @return true if it uses only RSRP for the number of LTE signal bar, otherwise false.
+     *
+     * @hide
+     */
+    public boolean useOnlyRsrpForLteLevel() {
+        return this.mUseOnlyRsrpForLteLevel;
+    }
+
+    /**
      * @return get TD_SCDMA dbm
      *
      * @hide
@@ -974,7 +1009,8 @@
                 + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)
                 + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
                 + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
-                + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0));
+                + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
+                + (mUseOnlyRsrpForLteLevel ? 1 : 0));
     }
 
     /**
@@ -1008,7 +1044,8 @@
                 && mLteCqi == s.mLteCqi
                 && mLteRsrpBoost == s.mLteRsrpBoost
                 && mTdScdmaRscp == s.mTdScdmaRscp
-                && isGsm == s.isGsm);
+                && isGsm == s.isGsm
+                && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
     }
 
     /**
@@ -1031,7 +1068,9 @@
                 + " " + mLteCqi
                 + " " + mLteRsrpBoost
                 + " " + mTdScdmaRscp
-                + " " + (isGsm ? "gsm|lte" : "cdma"));
+                + " " + (isGsm ? "gsm|lte" : "cdma")
+                + " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
+                         "use_rsrp_and_rssnr_for_lte_level"));
     }
 
     /** Returns the signal strength related to GSM. */
@@ -1086,6 +1125,7 @@
         mLteRsrpBoost = m.getInt("lteRsrpBoost");
         mTdScdmaRscp = m.getInt("TdScdma");
         isGsm = m.getBoolean("isGsm");
+        mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
     }
 
     /**
@@ -1110,6 +1150,7 @@
         m.putInt("lteRsrpBoost", mLteRsrpBoost);
         m.putInt("TdScdma", mTdScdmaRscp);
         m.putBoolean("isGsm", isGsm);
+        m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
     }
 
     /**
diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java
index 98195ad..5d03926 100644
--- a/android/telephony/SmsManager.java
+++ b/android/telephony/SmsManager.java
@@ -338,16 +338,18 @@
     /**
      * Send a text based SMS without writing it into the SMS Provider.
      *
+     * <p>
+     * The message will be sent directly over the network and will not be visible in SMS
+     * applications. Intended for internal carrier use only.
+     * </p>
+     *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
      * privileges.
      * </p>
      *
      * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
-     * @hide
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void sendTextMessageWithoutPersisting(
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -387,6 +389,112 @@
     }
 
     /**
+     * Send a text based SMS with messaging options.
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     *
+     * @throws IllegalArgumentException if destinationAddress or text are empty
+     * {@hide}
+     */
+    public void sendTextMessage(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                true /* persistMessage*/, priority, expectMore, validityPeriod);
+    }
+
+    private void sendTextMessageInternal(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+            int priority, boolean expectMore, int validityPeriod) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+
+        if (TextUtils.isEmpty(text)) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        if (priority < 0x00 || priority > 0x03) {
+            throw new IllegalArgumentException("Invalid priority");
+        }
+
+        if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+            throw new IllegalArgumentException("Invalid validity period");
+        }
+
+        try {
+             ISms iccISms = getISmsServiceOrThrow();
+            if (iccISms != null) {
+                iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
+                        ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
+                        sentIntent, deliveryIntent, persistMessage,  priority, expectMore,
+                        validityPeriod);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Send a text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendTextMessage(String, String, String, PendingIntent,
+     * PendingIntent, int, boolean, int)
+     * @hide
+     */
+    public void sendTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
+            boolean expectMore, int validityPeriod) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                false /* persistMessage */, priority, expectMore, validityPeriod);
+    }
+
+    /**
+     *
      * Inject an SMS PDU into the android application framework.
      *
      * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
@@ -544,6 +652,140 @@
     }
 
     /**
+     * Send a multi-part text based SMS with messaging options. The callee should have already
+     * divided the message into correctly sized parts by calling
+     * <code>divideMessage</code>.
+     *
+     * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+     * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+     *
+     * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+     * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+     * writes messages sent using this method to the SMS Provider (the default SMS app is always
+     * responsible for writing its sent messages to the SMS Provider). For information about
+     * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
+     *   or one of these errors:<br>
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *   <code>RESULT_ERROR_NULL_PDU</code><br>
+     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+     *   the extra "errorCode" containing a radio technology specific value,
+     *   generally only useful for troubleshooting.<br>
+     *   The per-application based SMS control checks sentIntent. If sentIntent
+     *   is NULL the caller will be checked against all unknown applications,
+     *   which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     * {@hide}
+     */
+    public void sendMultipartTextMessage(
+            String destinationAddress, String scAddress, ArrayList<String> parts,
+            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, true /* persistMessage*/);
+    }
+
+    private void sendMultipartTextMessageInternal(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+        if (parts == null || parts.size() < 1) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        if (priority < 0x00 || priority > 0x03) {
+            throw new IllegalArgumentException("Invalid priority");
+        }
+
+        if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+            throw new IllegalArgumentException("Invalid validity period");
+        }
+
+        if (parts.size() > 1) {
+            try {
+                 ISms iccISms = getISmsServiceOrThrow();
+                if (iccISms != null) {
+                    iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
+                            ActivityThread.currentPackageName(), destinationAddress, scAddress,
+                            parts, sentIntents, deliveryIntents, persistMessage, priority,
+                            expectMore, validityPeriod);
+                }
+            } catch (RemoteException ex) {
+                // ignore it
+            }
+        } else {
+            PendingIntent sentIntent = null;
+            PendingIntent deliveryIntent = null;
+            if (sentIntents != null && sentIntents.size() > 0) {
+                sentIntent = sentIntents.get(0);
+            }
+            if (deliveryIntents != null && deliveryIntents.size() > 0) {
+                deliveryIntent = deliveryIntents.get(0);
+            }
+            sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
+                    sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+                    validityPeriod);
+        }
+    }
+
+    /**
+     * Send a multi-part text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList,
+     * ArrayList, int, boolean, int)
+     * @hide
+     **/
+    public void sendMultipartTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, false /* persistMessage*/, priority, expectMore,
+                validityPeriod);
+    }
+
+   /**
      * Send a data based SMS to a specific application port.
      *
      * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
@@ -1006,7 +1248,7 @@
      *   <code>getAllMessagesFromIcc</code>
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
      */
-    private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+    private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
         ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
         if (records != null) {
             int count = records.size();
@@ -1014,7 +1256,8 @@
                 SmsRawData data = records.get(i);
                 // List contains all records, including "free" records (null)
                 if (data != null) {
-                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
+                            getSubscriptionId());
                     if (sms != null) {
                         messages.add(sms);
                     }
diff --git a/android/telephony/SmsMessage.java b/android/telephony/SmsMessage.java
index df41233..a5d67c6 100644
--- a/android/telephony/SmsMessage.java
+++ b/android/telephony/SmsMessage.java
@@ -271,6 +271,31 @@
     }
 
     /**
+     * Create an SmsMessage from an SMS EF record.
+     *
+     * @param index Index of SMS record. This should be index in ArrayList
+     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param data Record data.
+     * @param subId Subscription Id of the SMS
+     * @return An SmsMessage representing the record.
+     *
+     * @hide
+     */
+    public static SmsMessage createFromEfRecord(int index, byte[] data, int subId) {
+        SmsMessageBase wrappedMessage;
+
+        if (isCdmaVoice(subId)) {
+            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+                    index, data);
+        } else {
+            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+                    index, data);
+        }
+
+        return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+    }
+
+    /**
      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
      * length in bytes (not hex chars) less the SMSC header
      *
@@ -822,6 +847,7 @@
          int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
          return (PHONE_TYPE_CDMA == activePhone);
    }
+
     /**
      * Decide if the carrier supports long SMS.
      * {@hide}
diff --git a/android/telephony/SubscriptionManager.java b/android/telephony/SubscriptionManager.java
index 88f4880..2f39ddb 100644
--- a/android/telephony/SubscriptionManager.java
+++ b/android/telephony/SubscriptionManager.java
@@ -360,6 +360,42 @@
     public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
 
     /**
+     * TelephonyProvider column name for enable Volte.
+     *@hide
+     */
+    public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+
+    /**
+     * TelephonyProvider column name for enable VT (Video Telephony over IMS)
+     *@hide
+     */
+    public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+
+    /**
+     * TelephonyProvider column name for enable Wifi calling
+     *@hide
+     */
+    public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+    /**
+     * TelephonyProvider column name for Wifi calling mode
+     *@hide
+     */
+    public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+    /**
+     * TelephonyProvider column name for Wifi calling mode in roaming
+     *@hide
+     */
+    public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+
+    /**
+     * TelephonyProvider column name for enable Wifi calling in roaming
+     *@hide
+     */
+    public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+    /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
      *
diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java
index c0564c5..4ffb3c3 100644
--- a/android/telephony/TelephonyManager.java
+++ b/android/telephony/TelephonyManager.java
@@ -953,6 +953,27 @@
      */
     public static final int USSD_ERROR_SERVICE_UNAVAIL = -2;
 
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which leaves the roaming
+     * mode set to the radio default or to the user's preference if they've indicated one.
+     */
+    public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which only permits
+     * connections on home networks.
+     */
+    public static final int CDMA_ROAMING_MODE_HOME = 0;
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+     * affiliated networks.
+     */
+    public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+     * any network.
+     */
+    public static final int CDMA_ROAMING_MODE_ANY = 2;
+
     //
     //
     // Device Info
@@ -2145,13 +2166,16 @@
      * @hide
      */
     public String getSimOperatorNumeric() {
-        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        int subId = mSubId;
         if (!SubscriptionManager.isUsableSubIdValue(subId)) {
-            subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+            subId = SubscriptionManager.getDefaultDataSubscriptionId();
             if (!SubscriptionManager.isUsableSubIdValue(subId)) {
-                subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                subId = SubscriptionManager.getDefaultSmsSubscriptionId();
                 if (!SubscriptionManager.isUsableSubIdValue(subId)) {
-                    subId = SubscriptionManager.getDefaultSubscriptionId();
+                    subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                    if (!SubscriptionManager.isUsableSubIdValue(subId)) {
+                        subId = SubscriptionManager.getDefaultSubscriptionId();
+                    }
                 }
             }
         }
@@ -5685,29 +5709,6 @@
         return retVal;
     }
 
-    /**
-     * Returns the result and response from RIL for oem request
-     *
-     * @param oemReq the data is sent to ril.
-     * @param oemResp the respose data from RIL.
-     * @return negative value request was not handled or get error
-     *         0 request was handled succesfully, but no response data
-     *         positive value success, data length of response
-     * @hide
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @Deprecated
-    public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.invokeOemRilRequestRaw(oemReq, oemResp);
-        } catch (RemoteException ex) {
-        } catch (NullPointerException ex) {
-        }
-        return -1;
-    }
-
     /** @hide */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
diff --git a/android/telephony/TelephonyScanManager.java b/android/telephony/TelephonyScanManager.java
index 92a21b6..7bcdcdc 100644
--- a/android/telephony/TelephonyScanManager.java
+++ b/android/telephony/TelephonyScanManager.java
@@ -73,8 +73,8 @@
         /**
          * Informs the user that there is some error about the scan.
          *
-         * This callback will be called whenever there is any error about the scan, but the scan
-         * won't stop unless the onComplete() callback is called.
+         * This callback will be called whenever there is any error about the scan, and the scan
+         * will be terminated. onComplete() will NOT be called.
          */
         public void onError(int error) {}
     }
diff --git a/android/telephony/euicc/DownloadableSubscription.java b/android/telephony/euicc/DownloadableSubscription.java
index b5484e3..01041c8 100644
--- a/android/telephony/euicc/DownloadableSubscription.java
+++ b/android/telephony/euicc/DownloadableSubscription.java
@@ -53,6 +53,8 @@
     @Nullable
     public final String encodedActivationCode;
 
+    @Nullable private String confirmationCode;
+
     // see getCarrierName and setCarrierName
     @Nullable
     private String carrierName;
@@ -66,6 +68,7 @@
 
     private DownloadableSubscription(Parcel in) {
         encodedActivationCode = in.readString();
+        confirmationCode = in.readString();
         carrierName = in.readString();
         accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
     }
@@ -83,6 +86,21 @@
     }
 
     /**
+     * Sets the confirmation code.
+     */
+    public void setConfirmationCode(String confirmationCode) {
+        this.confirmationCode = confirmationCode;
+    }
+
+    /**
+     * Returns the confirmation code.
+     */
+    @Nullable
+    public String getConfirmationCode() {
+        return confirmationCode;
+    }
+
+    /**
      * Set the user-visible carrier name.
      * @hide
      *
@@ -134,6 +152,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(encodedActivationCode);
+        dest.writeString(confirmationCode);
         dest.writeString(carrierName);
         dest.writeTypedArray(accessRules, flags);
     }
diff --git a/android/telephony/ims/ImsServiceProxy.java b/android/telephony/ims/ImsServiceProxy.java
deleted file mode 100644
index 038e295..0000000
--- a/android/telephony/ims/ImsServiceProxy.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.ims;
-
-import android.app.PendingIntent;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.telephony.ims.feature.IRcsFeature;
-import android.telephony.ims.feature.ImsFeature;
-import android.util.Log;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
-import com.android.ims.internal.IImsUt;
-
-/**
- * A container of the IImsServiceController binder, which implements all of the ImsFeatures that
- * the platform currently supports: MMTel and RCS.
- * @hide
- */
-
-public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeature {
-
-    protected String LOG_TAG = "ImsServiceProxy";
-    private final int mSupportedFeature;
-
-    // Start by assuming the proxy is available for usage.
-    private boolean mIsAvailable = true;
-    // ImsFeature Status from the ImsService. Cached.
-    private Integer mFeatureStatusCached = null;
-    private ImsServiceProxy.INotifyStatusChanged mStatusCallback;
-    private final Object mLock = new Object();
-
-    public interface INotifyStatusChanged {
-        void notifyStatusChanged();
-    }
-
-    private final IImsServiceFeatureListener mListenerBinder =
-            new IImsServiceFeatureListener.Stub() {
-
-        @Override
-        public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
-            // The feature has been re-enabled. This may happen when the service crashes.
-            synchronized (mLock) {
-                if (!mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) {
-                    Log.i(LOG_TAG, "Feature enabled on slotId: " + slotId + " for feature: " +
-                            feature);
-                    mIsAvailable = true;
-                }
-            }
-        }
-
-        @Override
-        public void imsFeatureRemoved(int slotId, int feature) throws RemoteException {
-            synchronized (mLock) {
-                if (mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) {
-                    Log.i(LOG_TAG, "Feature disabled on slotId: " + slotId + " for feature: " +
-                            feature);
-                    mIsAvailable = false;
-                }
-            }
-        }
-
-        @Override
-        public void imsStatusChanged(int slotId, int feature, int status) throws RemoteException {
-            synchronized (mLock) {
-                Log.i(LOG_TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature +
-                        " status: " + status);
-                if (mSlotId == slotId && feature == mSupportedFeature) {
-                    mFeatureStatusCached = status;
-                    if (mStatusCallback != null) {
-                        mStatusCallback.notifyStatusChanged();
-                    }
-                }
-            }
-        }
-    };
-
-    public ImsServiceProxy(int slotId, IBinder binder, int featureType) {
-        super(slotId, binder);
-        mSupportedFeature = featureType;
-    }
-
-    public ImsServiceProxy(int slotId, int featureType) {
-        super(slotId, null /*IBinder*/);
-        mSupportedFeature = featureType;
-    }
-
-    public IImsServiceFeatureListener getListener() {
-        return mListenerBinder;
-    }
-
-    public void setBinder(IBinder binder) {
-        mBinder = binder;
-    }
-
-    @Override
-    public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
-            throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
-                    incomingCallIntent, listener);
-        }
-    }
-
-    @Override
-    public void endSession(int sessionId) throws RemoteException {
-        synchronized (mLock) {
-            // Only check to make sure the binder connection still exists. This method should
-            // still be able to be called when the state is STATE_NOT_AVAILABLE.
-            checkBinderConnection();
-            getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
-        }
-    }
-
-    @Override
-    public boolean isConnected(int callServiceType, int callType)
-            throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
-                    callServiceType, callType);
-        }
-    }
-
-    @Override
-    public boolean isOpened() throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
-        }
-    }
-
-    @Override
-    public void addRegistrationListener(IImsRegistrationListener listener)
-    throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
-                    listener);
-        }
-    }
-
-    @Override
-    public void removeRegistrationListener(IImsRegistrationListener listener)
-            throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
-                    listener);
-        }
-    }
-
-    @Override
-    public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
-            throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
-                    sessionId, callServiceType, callType);
-        }
-    }
-
-    @Override
-    public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
-            IImsCallSessionListener listener) throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
-                    sessionId, profile, listener);
-        }
-    }
-
-    @Override
-    public IImsCallSession getPendingCallSession(int sessionId, String callId)
-            throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
-                    sessionId, callId);
-        }
-    }
-
-    @Override
-    public IImsUt getUtInterface() throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
-        }
-    }
-
-    @Override
-    public IImsConfig getConfigInterface() throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
-        }
-    }
-
-    @Override
-    public void turnOnIms() throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
-        }
-    }
-
-    @Override
-    public void turnOffIms() throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
-        }
-    }
-
-    @Override
-    public IImsEcbm getEcbmInterface() throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
-        }
-    }
-
-    @Override
-    public void setUiTTYMode(int uiTtyMode, Message onComplete)
-            throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
-                    onComplete);
-        }
-    }
-
-    @Override
-    public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
-        synchronized (mLock) {
-            checkServiceIsReady();
-            return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
-                    mSupportedFeature);
-        }
-    }
-
-    @Override
-    public int getFeatureStatus() {
-        synchronized (mLock) {
-            if (isBinderAlive() && mFeatureStatusCached != null) {
-                Log.i(LOG_TAG, "getFeatureStatus - returning cached: " + mFeatureStatusCached);
-                return mFeatureStatusCached;
-            }
-        }
-        // Don't synchronize on Binder call.
-        Integer status = retrieveFeatureStatus();
-        synchronized (mLock) {
-            if (status == null) {
-                return ImsFeature.STATE_NOT_AVAILABLE;
-            }
-            // Cache only non-null value for feature status.
-            mFeatureStatusCached = status;
-        }
-        Log.i(LOG_TAG, "getFeatureStatus - returning " + status);
-        return status;
-    }
-
-    /**
-     * Internal method used to retrieve the feature status from the corresponding ImsService.
-     */
-    private Integer retrieveFeatureStatus() {
-        if (mBinder != null) {
-            try {
-                return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature);
-            } catch (RemoteException e) {
-                // Status check failed, don't update cache
-            }
-        }
-        return null;
-    }
-
-    /**
-     * @param c Callback that will fire when the feature status has changed.
-     */
-    public void setStatusCallback(INotifyStatusChanged c) {
-        mStatusCallback = c;
-    }
-
-    /**
-     * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
-     * method returns false, it doesn't mean that the Binder connection is not available (use
-     * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
-     * at this time.
-     *
-     * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
-     * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
-     */
-    public boolean isBinderReady() {
-        return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
-    }
-
-    @Override
-    public boolean isBinderAlive() {
-        return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
-    }
-
-    protected void checkServiceIsReady() throws RemoteException {
-        if (!isBinderReady()) {
-            throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
-        }
-    }
-
-    private IImsServiceController getServiceInterface(IBinder b) {
-        return IImsServiceController.Stub.asInterface(b);
-    }
-}
diff --git a/android/telephony/ims/ImsServiceProxyCompat.java b/android/telephony/ims/ImsServiceProxyCompat.java
deleted file mode 100644
index bbd5f02..0000000
--- a/android/telephony/ims/ImsServiceProxyCompat.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.ims;
-
-import android.app.PendingIntent;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.telephony.ims.feature.IMMTelFeature;
-import android.telephony.ims.feature.ImsFeature;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsService;
-import com.android.ims.internal.IImsUt;
-
-/**
- * Compatibility class that implements the new ImsService IMMTelFeature interface, but
- * uses the old IImsService interface to support older devices that implement the deprecated
- * opt/net/ims interface.
- * @hide
- */
-
-public class ImsServiceProxyCompat implements IMMTelFeature {
-
-    private static final int SERVICE_ID = ImsFeature.MMTEL;
-
-    protected final int mSlotId;
-    protected IBinder mBinder;
-
-    public ImsServiceProxyCompat(int slotId, IBinder binder) {
-        mSlotId = slotId;
-        mBinder = binder;
-    }
-
-    @Override
-    public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
-            throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).open(mSlotId, ImsFeature.MMTEL, incomingCallIntent,
-                listener);
-    }
-
-    @Override
-    public void endSession(int sessionId) throws RemoteException {
-        checkBinderConnection();
-        getServiceInterface(mBinder).close(sessionId);
-    }
-
-    @Override
-    public boolean isConnected(int callServiceType, int callType)
-            throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).isConnected(SERVICE_ID,  callServiceType, callType);
-    }
-
-    @Override
-    public boolean isOpened() throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).isOpened(SERVICE_ID);
-    }
-
-    @Override
-    public void addRegistrationListener(IImsRegistrationListener listener)
-            throws RemoteException {
-        checkBinderConnection();
-        getServiceInterface(mBinder).addRegistrationListener(mSlotId, ImsFeature.MMTEL, listener);
-    }
-
-    @Override
-    public void removeRegistrationListener(IImsRegistrationListener listener)
-            throws RemoteException {
-        // Not Implemented in old ImsService. If the registration listener becomes invalid, the
-        // ImsService will remove.
-    }
-
-    @Override
-    public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
-            throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType, callType);
-    }
-
-    @Override
-    public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
-            IImsCallSessionListener listener) throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener);
-    }
-
-    @Override
-    public IImsCallSession getPendingCallSession(int sessionId, String callId)
-            throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId);
-    }
-
-    @Override
-    public IImsUt getUtInterface() throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).getUtInterface(SERVICE_ID);
-    }
-
-    @Override
-    public IImsConfig getConfigInterface() throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).getConfigInterface(mSlotId);
-    }
-
-    @Override
-    public void turnOnIms() throws RemoteException {
-        checkBinderConnection();
-        getServiceInterface(mBinder).turnOnIms(mSlotId);
-    }
-
-    @Override
-    public void turnOffIms() throws RemoteException {
-        checkBinderConnection();
-        getServiceInterface(mBinder).turnOffIms(mSlotId);
-    }
-
-    @Override
-    public IImsEcbm getEcbmInterface() throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).getEcbmInterface(SERVICE_ID);
-    }
-
-    @Override
-    public void setUiTTYMode(int uiTtyMode, Message onComplete)
-            throws RemoteException {
-        checkBinderConnection();
-        getServiceInterface(mBinder).setUiTTYMode(SERVICE_ID, uiTtyMode, onComplete);
-    }
-
-    @Override
-    public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
-        checkBinderConnection();
-        return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID);
-    }
-
-    /**
-     * Base implementation, always returns READY for compatibility with old ImsService.
-     */
-    public int getFeatureStatus() {
-        return ImsFeature.STATE_READY;
-    }
-
-    /**
-     * @return false if the binder connection is no longer alive.
-     */
-    public boolean isBinderAlive() {
-        return mBinder != null && mBinder.isBinderAlive();
-    }
-
-    private IImsService getServiceInterface(IBinder b) {
-        return IImsService.Stub.asInterface(b);
-    }
-
-    protected void checkBinderConnection() throws RemoteException {
-        if (!isBinderAlive()) {
-            throw new RemoteException("ImsServiceProxy is not available for that feature.");
-        }
-    }
-}
diff --git a/android/telephony/ims/feature/IMMTelFeature.java b/android/telephony/ims/feature/IMMTelFeature.java
deleted file mode 100644
index d65e27e..0000000
--- a/android/telephony/ims/feature/IMMTelFeature.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.ims.feature;
-
-import android.app.PendingIntent;
-import android.os.Message;
-import android.os.RemoteException;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsUt;
-
-/**
- * MMTel interface for an ImsService. When updating this interface, ensure that base implementations
- * of your changes are also present in MMTelFeature for compatibility with older versions of the
- * MMTel feature.
- * @hide
- */
-
-public interface IMMTelFeature {
-
-    /**
-     * Notifies the MMTel feature that you would like to start a session. This should always be
-     * done before making/receiving IMS calls. The IMS service will register the device to the
-     * operator's network with the credentials (from ISIM) periodically in order to receive calls
-     * from the operator's network. When the IMS service receives a new call, it will send out an
-     * intent with the provided action string. The intent contains a call ID extra
-     * {@link IImsCallSession#getCallId} and it can be used to take a call.
-     *
-     * @param incomingCallIntent When an incoming call is received, the IMS service will call
-     * {@link PendingIntent#send} to send back the intent to the caller with
-     * {@link #INCOMING_CALL_RESULT_CODE} as the result code and the intent to fill in the call ID;
-     * It cannot be null.
-     * @param listener To listen to IMS registration events; It cannot be null
-     * @return an integer (greater than 0) representing the session id associated with the session
-     * that has been started.
-     */
-    int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
-            throws RemoteException;
-
-    /**
-     * End a previously started session using the associated sessionId.
-     * @param sessionId an integer (greater than 0) representing the ongoing session. See
-     * {@link #startSession}.
-     */
-    void endSession(int sessionId) throws RemoteException;
-
-    /**
-     * Checks if the IMS service has successfully registered to the IMS network with the specified
-     * service & call type.
-     *
-     * @param callServiceType a service type that is specified in {@link ImsCallProfile}
-     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
-     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
-     * @param callType a call type that is specified in {@link ImsCallProfile}
-     *        {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
-     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
-     *        {@link ImsCallProfile#CALL_TYPE_VT}
-     *        {@link ImsCallProfile#CALL_TYPE_VS}
-     * @return true if the specified service id is connected to the IMS network; false otherwise
-     * @throws RemoteException
-     */
-    boolean isConnected(int callServiceType, int callType) throws RemoteException;
-
-    /**
-     * Checks if the specified IMS service is opened.
-     *
-     * @return true if the specified service id is opened; false otherwise
-     */
-    boolean isOpened() throws RemoteException;
-
-    /**
-     * Add a new registration listener for the client associated with the session Id.
-     * @param listener An implementation of IImsRegistrationListener.
-     */
-    void addRegistrationListener(IImsRegistrationListener listener)
-            throws RemoteException;
-
-    /**
-     * Remove a previously registered listener using {@link #addRegistrationListener} for the client
-     * associated with the session Id.
-     * @param listener A previously registered IImsRegistrationListener
-     */
-    void removeRegistrationListener(IImsRegistrationListener listener)
-            throws RemoteException;
-
-    /**
-     * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
-     *
-     * @param sessionId a session id which is obtained from {@link #startSession}
-     * @param callServiceType a service type that is specified in {@link ImsCallProfile}
-     *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
-     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
-     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
-     * @param callType a call type that is specified in {@link ImsCallProfile}
-     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
-     *        {@link ImsCallProfile#CALL_TYPE_VT}
-     *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
-     *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
-     *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
-     *        {@link ImsCallProfile#CALL_TYPE_VS}
-     *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
-     *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
-     * @return a {@link ImsCallProfile} object
-     */
-    ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
-            throws RemoteException;
-
-    /**
-     * Creates a {@link ImsCallSession} with the specified call profile.
-     * Use other methods, if applicable, instead of interacting with
-     * {@link ImsCallSession} directly.
-     *
-     * @param sessionId a session id which is obtained from {@link #startSession}
-     * @param profile a call profile to make the call
-     * @param listener An implementation of IImsCallSessionListener.
-     */
-    IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
-            IImsCallSessionListener listener) throws RemoteException;
-
-    /**
-     * Retrieves the call session associated with a pending call.
-     *
-     * @param sessionId a session id which is obtained from {@link #startSession}
-     * @param callId a call id to make the call
-     */
-    IImsCallSession getPendingCallSession(int sessionId, String callId) throws RemoteException;
-
-    /**
-     * @return The Ut interface for the supplementary service configuration.
-     */
-    IImsUt getUtInterface() throws RemoteException;
-
-    /**
-     * @return The config interface for IMS Configuration
-     */
-    IImsConfig getConfigInterface() throws RemoteException;
-
-    /**
-     * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
-     * @param sessionId a session id which is obtained from {@link #startSession}
-     */
-    void turnOnIms() throws RemoteException;
-
-    /**
-     * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
-     * @param sessionId a session id which is obtained from {@link #startSession}
-     */
-    void turnOffIms() throws RemoteException;
-
-    /**
-     * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
-     */
-    IImsEcbm getEcbmInterface() throws RemoteException;
-
-    /**
-     * Sets the current UI TTY mode for the MMTelFeature.
-     * @param uiTtyMode An integer containing the new UI TTY Mode.
-     * @param onComplete A {@link Message} to be used when the mode has been set.
-     * @throws RemoteException
-     */
-    void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException;
-
-    /**
-     * @return MultiEndpoint interface for DEP notifications
-     */
-    IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException;
-}
diff --git a/android/telephony/ims/feature/MMTelFeature.java b/android/telephony/ims/feature/MMTelFeature.java
index a71f0bf..758c379 100644
--- a/android/telephony/ims/feature/MMTelFeature.java
+++ b/android/telephony/ims/feature/MMTelFeature.java
@@ -32,90 +32,183 @@
 import java.util.List;
 
 /**
- * Base implementation, which implements all methods in IMMTelFeature. Any class wishing to use
- * MMTelFeature should extend this class and implement all methods that the service supports.
+ * Base implementation for MMTel.
+ * Any class wishing to use MMTelFeature should extend this class and implement all methods that the
+ * service supports.
  *
  * @hide
  */
 
-public class MMTelFeature extends ImsFeature implements IMMTelFeature {
+public class MMTelFeature extends ImsFeature {
 
-    @Override
+    /**
+     * Notifies the MMTel feature that you would like to start a session. This should always be
+     * done before making/receiving IMS calls. The IMS service will register the device to the
+     * operator's network with the credentials (from ISIM) periodically in order to receive calls
+     * from the operator's network. When the IMS service receives a new call, it will send out an
+     * intent with the provided action string. The intent contains a call ID extra
+     * {@link IImsCallSession#getCallId} and it can be used to take a call.
+     *
+     * @param incomingCallIntent When an incoming call is received, the IMS service will call
+     * {@link PendingIntent#send} to send back the intent to the caller with
+     * ImsManager#INCOMING_CALL_RESULT_CODE as the result code and the intent to fill in the call
+     * ID; It cannot be null.
+     * @param listener To listen to IMS registration events; It cannot be null
+     * @return an integer (greater than 0) representing the session id associated with the session
+     * that has been started.
+     */
     public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) {
         return 0;
     }
 
-    @Override
+    /**
+     * End a previously started session using the associated sessionId.
+     * @param sessionId an integer (greater than 0) representing the ongoing session. See
+     * {@link #startSession}.
+     */
     public void endSession(int sessionId) {
     }
 
-    @Override
+    /**
+     * Checks if the IMS service has successfully registered to the IMS network with the specified
+     * service & call type.
+     *
+     * @param callSessionType a service type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+     * @param callType a call type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
+     *        {@link ImsCallProfile#CALL_TYPE_VT}
+     *        {@link ImsCallProfile#CALL_TYPE_VS}
+     * @return true if the specified service id is connected to the IMS network; false otherwise
+     */
     public boolean isConnected(int callSessionType, int callType) {
         return false;
     }
 
-    @Override
+    /**
+     * Checks if the specified IMS service is opened.
+     *
+     * @return true if the specified service id is opened; false otherwise
+     */
     public boolean isOpened() {
         return false;
     }
 
-    @Override
+    /**
+     * Add a new registration listener for the client associated with the session Id.
+     * @param listener An implementation of IImsRegistrationListener.
+     */
     public void addRegistrationListener(IImsRegistrationListener listener) {
     }
 
-    @Override
+    /**
+     * Remove a previously registered listener using {@link #addRegistrationListener} for the client
+     * associated with the session Id.
+     * @param listener A previously registered IImsRegistrationListener
+     */
     public void removeRegistrationListener(IImsRegistrationListener listener) {
     }
 
-    @Override
+    /**
+     * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
+     *
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     * @param callSessionType a service type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+     * @param callType a call type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
+     *        {@link ImsCallProfile#CALL_TYPE_VT}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
+     *        {@link ImsCallProfile#CALL_TYPE_VS}
+     *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
+     *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
+     * @return a {@link ImsCallProfile} object
+     */
     public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) {
         return null;
     }
 
-    @Override
+    /**
+     * Creates a {@link ImsCallSession} with the specified call profile.
+     * Use other methods, if applicable, instead of interacting with
+     * {@link ImsCallSession} directly.
+     *
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     * @param profile a call profile to make the call
+     * @param listener An implementation of IImsCallSessionListener.
+     */
     public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
             IImsCallSessionListener listener) {
         return null;
     }
 
-    @Override
+    /**
+     * Retrieves the call session associated with a pending call.
+     *
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     * @param callId a call id to make the call
+     */
     public IImsCallSession getPendingCallSession(int sessionId, String callId) {
         return null;
     }
 
-    @Override
+    /**
+     * @return The Ut interface for the supplementary service configuration.
+     */
     public IImsUt getUtInterface() {
         return null;
     }
 
-    @Override
+    /**
+     * @return The config interface for IMS Configuration
+     */
     public IImsConfig getConfigInterface() {
         return null;
     }
 
-    @Override
+    /**
+     * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
+     */
     public void turnOnIms() {
     }
 
-    @Override
+    /**
+     * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
+     */
     public void turnOffIms() {
     }
 
-    @Override
+    /**
+     * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
+     */
     public IImsEcbm getEcbmInterface() {
         return null;
     }
 
-    @Override
+    /**
+     * Sets the current UI TTY mode for the MMTelFeature.
+     * @param uiTtyMode An integer containing the new UI TTY Mode.
+     * @param onComplete A {@link Message} to be used when the mode has been set.
+     */
     public void setUiTTYMode(int uiTtyMode, Message onComplete) {
     }
 
-    @Override
+    /**
+     * @return MultiEndpoint interface for DEP notifications
+     */
     public IImsMultiEndpoint getMultiEndpointInterface() {
         return null;
     }
 
-    @Override
+    /**
+     * {@inheritDoc}
+     */
     public void onFeatureRemoved() {
 
     }
diff --git a/android/telephony/ims/feature/RcsFeature.java b/android/telephony/ims/feature/RcsFeature.java
index 9cddc1b..332cca3 100644
--- a/android/telephony/ims/feature/RcsFeature.java
+++ b/android/telephony/ims/feature/RcsFeature.java
@@ -18,11 +18,11 @@
 
 /**
  * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
- * this class and provide implementations of the IRcsFeature methods that they support.
+ * this class and provide implementations of the RcsFeature methods that they support.
  * @hide
  */
 
-public class RcsFeature extends ImsFeature implements IRcsFeature {
+public class RcsFeature extends ImsFeature {
 
     public RcsFeature() {
         super();
diff --git a/android/telephony/mbms/DownloadStateCallback.java b/android/telephony/mbms/DownloadStateCallback.java
index 892fbf0..9f60cc3 100644
--- a/android/telephony/mbms/DownloadStateCallback.java
+++ b/android/telephony/mbms/DownloadStateCallback.java
@@ -38,7 +38,7 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+    @IntDef(flag = true, value = {ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
     public @interface FilterFlag {}
 
     /**
diff --git a/android/telephony/mbms/FileInfo.java b/android/telephony/mbms/FileInfo.java
index 0d737b5..e064adb 100644
--- a/android/telephony/mbms/FileInfo.java
+++ b/android/telephony/mbms/FileInfo.java
@@ -17,10 +17,13 @@
 package android.telephony.mbms;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Describes a single file that is available over MBMS.
  */
@@ -47,6 +50,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public FileInfo(Uri uri, String mimeType) {
         this.uri = uri;
         this.mimeType = mimeType;
@@ -82,4 +86,23 @@
     public String getMimeType() {
         return mimeType;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        FileInfo fileInfo = (FileInfo) o;
+        return Objects.equals(uri, fileInfo.uri) &&
+                Objects.equals(mimeType, fileInfo.mimeType);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(uri, mimeType);
+    }
 }
diff --git a/android/telephony/mbms/FileServiceInfo.java b/android/telephony/mbms/FileServiceInfo.java
index d8d7f48..b30a3af 100644
--- a/android/telephony/mbms/FileServiceInfo.java
+++ b/android/telephony/mbms/FileServiceInfo.java
@@ -17,6 +17,7 @@
 package android.telephony.mbms;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,6 +36,7 @@
 
     /** @hide */
     @SystemApi
+    @TestApi
     public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
             List<Locale> newLocales, String newServiceId, Date start, Date end,
             List<FileInfo> newFiles) {
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index 9af1eb9..9ef188c 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,16 +165,16 @@
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
                 return false;
             }
+            if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
+                Log.w(LOG_TAG, "Download result did not include the associated request. 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;
-            }
             if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
                 return false;
@@ -242,10 +242,12 @@
         int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
                 MbmsDownloadSession.RESULT_CANCELLED);
         intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
+        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
 
         if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
             Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
             context.sendBroadcast(intentForApp);
+            setResultCode(RESULT_OK);
             return;
         }
 
@@ -273,7 +275,6 @@
         intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
                 stagedFileLocation);
         intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
-        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
 
         context.sendBroadcast(intentForApp);
         setResultCode(RESULT_OK);
diff --git a/android/telephony/mbms/UriPathPair.java b/android/telephony/mbms/UriPathPair.java
index 187e9ee..dd20a69 100644
--- a/android/telephony/mbms/UriPathPair.java
+++ b/android/telephony/mbms/UriPathPair.java
@@ -17,6 +17,7 @@
 package android.telephony.mbms;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Parcel;
@@ -29,6 +30,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public final class UriPathPair implements Parcelable {
     private final Uri mFilePathUri;
     private final Uri mContentUri;
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 9ccdd56..4fee3df 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.IBinder;
@@ -42,6 +43,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
     private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
     private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
diff --git a/android/telephony/mbms/vendor/VendorUtils.java b/android/telephony/mbms/vendor/VendorUtils.java
index a43f122..f1cac8c 100644
--- a/android/telephony/mbms/vendor/VendorUtils.java
+++ b/android/telephony/mbms/vendor/VendorUtils.java
@@ -17,6 +17,7 @@
 package android.telephony.mbms.vendor;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -34,6 +35,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class VendorUtils {
 
     /**
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java
index fc1d487..2a27220 100644
--- a/android/util/FeatureFlagUtils.java
+++ b/android/util/FeatureFlagUtils.java
@@ -16,6 +16,7 @@
 
 package android.util;
 
+import android.content.Context;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 
@@ -37,7 +38,7 @@
      * @param feature the flag name
      * @return true if the flag is enabled (either by default in system, or override by user)
      */
-    public static boolean isEnabled(String feature) {
+    public static boolean isEnabled(Context context, String feature) {
         // Tries to get feature flag from system property.
         // Step 1: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
         String value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
diff --git a/android/util/KeyValueListParser.java b/android/util/KeyValueListParser.java
index be531ff..d50395e 100644
--- a/android/util/KeyValueListParser.java
+++ b/android/util/KeyValueListParser.java
@@ -147,4 +147,18 @@
         }
         return def;
     }
+
+    /**
+     * @return the number of keys.
+     */
+    public int size() {
+        return mValues.size();
+    }
+
+    /**
+     * @return the key at {@code index}. Use with {@link #size()} to enumerate all key-value pairs.
+     */
+    public String keyAt(int index) {
+        return mValues.keyAt(index);
+    }
 }
diff --git a/android/util/Log.java b/android/util/Log.java
index 0299865..b94e48b 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,45 +16,12 @@
 
 package android.util;
 
-import android.os.DeadSystemException;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.LineBreakBufferedWriter;
-
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.io.Writer;
 import java.net.UnknownHostException;
 
 /**
- * API for sending log output.
- *
- * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
- * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
- * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
- *
- * <p>The order in terms of verbosity, from least to most is
- * ERROR, WARN, INFO, DEBUG, VERBOSE.  Verbose should never be compiled
- * into an application except during development.  Debug logs are compiled
- * in but stripped at runtime.  Error, warning and info logs are always kept.
- *
- * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
- * in your class:
- *
- * <pre>private static final String TAG = "MyActivity";</pre>
- *
- * and use that in subsequent calls to the log methods.
- * </p>
- *
- * <p><b>Tip:</b> Don't forget that when you make a call like
- * <pre>Log.v(TAG, "index=" + i);</pre>
- * that when you're building the string to pass into Log.d, the compiler uses a
- * StringBuilder and at least three allocations occur: the StringBuilder
- * itself, the buffer, and the String object.  Realistically, there is also
- * another buffer allocation and copy, and even more pressure on the gc.
- * That means that if your log message is filtered out, you might be doing
- * significant work and incurring significant overhead.
+ * Mock Log implementation for testing on non android host.
  */
 public final class Log {
 
@@ -88,29 +55,6 @@
      */
     public static final int ASSERT = 7;
 
-    /**
-     * Exception class used to capture a stack trace in {@link #wtf}.
-     * @hide
-     */
-    public static class TerribleFailure extends Exception {
-        TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
-    }
-
-    /**
-     * Interface to handle terrible failures from {@link #wtf}.
-     *
-     * @hide
-     */
-    public interface TerribleFailureHandler {
-        void onTerribleFailure(String tag, TerribleFailure what, boolean system);
-    }
-
-    private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
-            public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
-                RuntimeInit.wtf(tag, what, system);
-            }
-        };
-
     private Log() {
     }
 
@@ -121,7 +65,7 @@
      * @param msg The message you would like logged.
      */
     public static int v(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
+        return println(LOG_ID_MAIN, VERBOSE, tag, msg);
     }
 
     /**
@@ -132,7 +76,7 @@
      * @param tr An exception to log
      */
     public static int v(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
+        return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -142,7 +86,7 @@
      * @param msg The message you would like logged.
      */
     public static int d(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
+        return println(LOG_ID_MAIN, DEBUG, tag, msg);
     }
 
     /**
@@ -153,7 +97,7 @@
      * @param tr An exception to log
      */
     public static int d(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
+        return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -163,7 +107,7 @@
      * @param msg The message you would like logged.
      */
     public static int i(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, INFO, tag, msg);
+        return println(LOG_ID_MAIN, INFO, tag, msg);
     }
 
     /**
@@ -174,7 +118,7 @@
      * @param tr An exception to log
      */
     public static int i(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
+        return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -184,7 +128,7 @@
      * @param msg The message you would like logged.
      */
     public static int w(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, WARN, tag, msg);
+        return println(LOG_ID_MAIN, WARN, tag, msg);
     }
 
     /**
@@ -195,31 +139,9 @@
      * @param tr An exception to log
      */
     public static int w(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
+        return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
     }
 
-    /**
-     * Checks to see whether or not a log for the specified tag is loggable at the specified level.
-     *
-     *  The default level of any tag is set to INFO. This means that any level above and including
-     *  INFO will be logged. Before you make any calls to a logging method you should check to see
-     *  if your tag should be logged. You can change the default level by setting a system property:
-     *      'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
-     *  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
-     *  turn off all logging for your tag. You can also create a local.prop file that with the
-     *  following in it:
-     *      'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
-     *  and place that in /data/local.prop.
-     *
-     * @param tag The tag to check.
-     * @param level The level to check.
-     * @return Whether or not that this is allowed to be logged.
-     * @throws IllegalArgumentException is thrown if the tag.length() > 23
-     *         for Nougat (7.0) releases (API <= 23) and prior, there is no
-     *         tag limit of concern after this API level.
-     */
-    public static native boolean isLoggable(String tag, int level);
-
     /*
      * Send a {@link #WARN} log message and log the exception.
      * @param tag Used to identify the source of a log message.  It usually identifies
@@ -227,7 +149,7 @@
      * @param tr An exception to log
      */
     public static int w(String tag, Throwable tr) {
-        return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
+        return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
     }
 
     /**
@@ -237,7 +159,7 @@
      * @param msg The message you would like logged.
      */
     public static int e(String tag, String msg) {
-        return println_native(LOG_ID_MAIN, ERROR, tag, msg);
+        return println(LOG_ID_MAIN, ERROR, tag, msg);
     }
 
     /**
@@ -248,82 +170,7 @@
      * @param tr An exception to log
      */
     public static int e(String tag, String msg, Throwable tr) {
-        return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
-    }
-
-    /**
-     * What a Terrible Failure: Report a condition that should never happen.
-     * The error will always be logged at level ASSERT with the call stack.
-     * Depending on system configuration, a report may be added to the
-     * {@link android.os.DropBoxManager} and/or the process may be terminated
-     * immediately with an error dialog.
-     * @param tag Used to identify the source of a log message.
-     * @param msg The message you would like logged.
-     */
-    public static int wtf(String tag, String msg) {
-        return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
-    }
-
-    /**
-     * Like {@link #wtf(String, String)}, but also writes to the log the full
-     * call stack.
-     * @hide
-     */
-    public static int wtfStack(String tag, String msg) {
-        return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
-    }
-
-    /**
-     * What a Terrible Failure: Report an exception that should never happen.
-     * Similar to {@link #wtf(String, String)}, with an exception to log.
-     * @param tag Used to identify the source of a log message.
-     * @param tr An exception to log.
-     */
-    public static int wtf(String tag, Throwable tr) {
-        return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
-    }
-
-    /**
-     * What a Terrible Failure: Report an exception that should never happen.
-     * Similar to {@link #wtf(String, Throwable)}, with a message as well.
-     * @param tag Used to identify the source of a log message.
-     * @param msg The message you would like logged.
-     * @param tr An exception to log.  May be null.
-     */
-    public static int wtf(String tag, String msg, Throwable tr) {
-        return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
-    }
-
-    static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
-            boolean system) {
-        TerribleFailure what = new TerribleFailure(msg, tr);
-        // Only mark this as ERROR, do not use ASSERT since that should be
-        // reserved for cases where the system is guaranteed to abort.
-        // The onTerribleFailure call does not always cause a crash.
-        int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
-        sWtfHandler.onTerribleFailure(tag, what, system);
-        return bytes;
-    }
-
-    static void wtfQuiet(int logId, String tag, String msg, boolean system) {
-        TerribleFailure what = new TerribleFailure(msg, null);
-        sWtfHandler.onTerribleFailure(tag, what, system);
-    }
-
-    /**
-     * Sets the terrible failure handler, for testing.
-     *
-     * @return the old handler
-     *
-     * @hide
-     */
-    public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
-        if (handler == null) {
-            throw new NullPointerException("handler == null");
-        }
-        TerribleFailureHandler oldHandler = sWtfHandler;
-        sWtfHandler = handler;
-        return oldHandler;
+        return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
     }
 
     /**
@@ -346,7 +193,7 @@
         }
 
         StringWriter sw = new StringWriter();
-        PrintWriter pw = new FastPrintWriter(sw, false, 256);
+        PrintWriter pw = new PrintWriter(sw);
         tr.printStackTrace(pw);
         pw.flush();
         return sw.toString();
@@ -361,7 +208,7 @@
      * @return The number of bytes written.
      */
     public static int println(int priority, String tag, String msg) {
-        return println_native(LOG_ID_MAIN, priority, tag, msg);
+        return println(LOG_ID_MAIN, priority, tag, msg);
     }
 
     /** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -370,115 +217,9 @@
     /** @hide */ public static final int LOG_ID_SYSTEM = 3;
     /** @hide */ public static final int LOG_ID_CRASH = 4;
 
-    /** @hide */ public static native int println_native(int bufID,
-            int priority, String tag, String msg);
-
-    /**
-     * Return the maximum payload the log daemon accepts without truncation.
-     * @return LOGGER_ENTRY_MAX_PAYLOAD.
-     */
-    private static native int logger_entry_max_payload_native();
-
-    /**
-     * Helper function for long messages. Uses the LineBreakBufferedWriter to break
-     * up long messages and stacktraces along newlines, but tries to write in large
-     * chunks. This is to avoid truncation.
-     * @hide
-     */
-    public static int printlns(int bufID, int priority, String tag, String msg,
-            Throwable tr) {
-        ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
-        // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
-        // and the length of the tag.
-        // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
-        //       is too expensive to compute that ahead of time.
-        int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD    // Base.
-                - 2                                                // Two terminators.
-                - (tag != null ? tag.length() : 0)                 // Tag length.
-                - 32;                                              // Some slack.
-        // At least assume you can print *some* characters (tag is not too large).
-        bufferSize = Math.max(bufferSize, 100);
-
-        LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
-
-        lbbw.println(msg);
-
-        if (tr != null) {
-            // This is to reduce the amount of log spew that apps do in the non-error
-            // condition of the network being unavailable.
-            Throwable t = tr;
-            while (t != null) {
-                if (t instanceof UnknownHostException) {
-                    break;
-                }
-                if (t instanceof DeadSystemException) {
-                    lbbw.println("DeadSystemException: The system died; "
-                            + "earlier logs will point to the root cause");
-                    break;
-                }
-                t = t.getCause();
-            }
-            if (t == null) {
-                tr.printStackTrace(lbbw);
-            }
-        }
-
-        lbbw.flush();
-
-        return logWriter.getWritten();
-    }
-
-    /**
-     * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
-     * a JNI call during logging.
-     */
-    static class PreloadHolder {
-        public final static int LOGGER_ENTRY_MAX_PAYLOAD =
-                logger_entry_max_payload_native();
-    }
-
-    /**
-     * Helper class to write to the logcat. Different from LogWriter, this writes
-     * the whole given buffer and does not break along newlines.
-     */
-    private static class ImmediateLogWriter extends Writer {
-
-        private int bufID;
-        private int priority;
-        private String tag;
-
-        private int written = 0;
-
-        /**
-         * Create a writer that immediately writes to the log, using the given
-         * parameters.
-         */
-        public ImmediateLogWriter(int bufID, int priority, String tag) {
-            this.bufID = bufID;
-            this.priority = priority;
-            this.tag = tag;
-        }
-
-        public int getWritten() {
-            return written;
-        }
-
-        @Override
-        public void write(char[] cbuf, int off, int len) {
-            // Note: using String here has a bit of overhead as a Java object is created,
-            //       but using the char[] directly is not easier, as it needs to be translated
-            //       to a C char[] for logging.
-            written += println_native(bufID, priority, tag, new String(cbuf, off, len));
-        }
-
-        @Override
-        public void flush() {
-            // Ignored.
-        }
-
-        @Override
-        public void close() {
-            // Ignored.
-        }
+    /** @hide */ @SuppressWarnings("unused")
+    public static int println(int bufID,
+            int priority, String tag, String msg) {
+        return 0;
     }
 }
diff --git a/android/util/StatsManager.java b/android/util/StatsManager.java
new file mode 100644
index 0000000..55b33a6
--- /dev/null
+++ b/android/util/StatsManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * API for StatsD clients to send configurations and retrieve data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsManager {
+    IStatsManager mService;
+    private static final String TAG = "StatsManager";
+
+    /**
+     * Constructor for StatsManagerClient.
+     *
+     * @hide
+     */
+    public StatsManager() {
+    }
+
+    /**
+     * Clients can send a configuration and simultaneously registers the name of a broadcast
+     * receiver that listens for when it should request data.
+     *
+     * @param configKey An arbitrary string that allows clients to track the configuration.
+     * @param config    Wire-encoded StatsDConfig proto that specifies metrics (and all
+     *                  dependencies eg, conditions and matchers).
+     * @param pkg       The package name to receive the broadcast.
+     * @param cls       The name of the class that receives the broadcast.
+     * @return true if successful
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    throw new RuntimeException("StatsD service connection lost");
+                }
+                return service.addConfiguration(configKey, config, pkg, cls);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connect to statsd when getting data");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Remove a configuration from logging.
+     *
+     * @param configKey Configuration key to remove.
+     * @return true if successful
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean removeConfiguration(String configKey) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    throw new RuntimeException("StatsD service connection lost");
+                }
+                return service.removeConfiguration(configKey);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connect to statsd when getting data");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Clients can request data with a binder call.
+     *
+     * @param configKey Configuration key to retrieve data from.
+     * @return Serialized ConfigMetricsReport proto. Returns null on failure.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public byte[] getData(String configKey) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    throw new RuntimeException("StatsD service connection lost");
+                }
+                return service.getData(configKey);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connecto statsd when getting data");
+                return null;
+            }
+        }
+    }
+
+    private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            synchronized (this) {
+                mService = null;
+            }
+        }
+    }
+
+    private IStatsManager getIStatsManagerLocked() throws RemoteException {
+        if (mService != null) {
+            return mService;
+        }
+        mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+        if (mService != null) {
+            mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+        }
+        return mService;
+    }
+}
diff --git a/android/util/TimeUtils.java b/android/util/TimeUtils.java
index 2b03ed6..cc4a0b6 100644
--- a/android/util/TimeUtils.java
+++ b/android/util/TimeUtils.java
@@ -340,6 +340,14 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    public static String formatDuration(long duration) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, 0);
+            return new String(sFormatStr, 0, len);
+        }
+    }
+
+    /** @hide Just for debugging; not internationalized. */
     public static void formatDuration(long duration, PrintWriter pw) {
         formatDuration(duration, pw, 0);
     }
diff --git a/android/util/apk/ApkSignatureSchemeV2Verifier.java b/android/util/apk/ApkSignatureSchemeV2Verifier.java
index a9ccae1..1808123 100644
--- a/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,9 +16,6 @@
 
 package android.util.apk;
 
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.Pair;
 
@@ -30,7 +27,6 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.nio.DirectByteBuffer;
 import java.security.DigestException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -122,40 +118,6 @@
     }
 
     /**
-     * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
-     * contained in the block against the file.
-     */
-    private static class SignatureInfo {
-        /** Contents of APK Signature Scheme v2 block. */
-        private final ByteBuffer signatureBlock;
-
-        /** Position of the APK Signing Block in the file. */
-        private final long apkSigningBlockOffset;
-
-        /** Position of the ZIP Central Directory in the file. */
-        private final long centralDirOffset;
-
-        /** Position of the ZIP End of Central Directory (EoCD) in the file. */
-        private final long eocdOffset;
-
-        /** Contents of ZIP End of Central Directory (EoCD) of the file. */
-        private final ByteBuffer eocd;
-
-        private SignatureInfo(
-                ByteBuffer signatureBlock,
-                long apkSigningBlockOffset,
-                long centralDirOffset,
-                long eocdOffset,
-                ByteBuffer eocd) {
-            this.signatureBlock = signatureBlock;
-            this.apkSigningBlockOffset = apkSigningBlockOffset;
-            this.centralDirOffset = centralDirOffset;
-            this.eocdOffset = eocdOffset;
-            this.eocd = eocd;
-        }
-    }
-
-    /**
      * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
      * additional information relevant for verifying the block against the file.
      *
@@ -497,6 +459,7 @@
         // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
         // into how to parallelize (if at all) based on the capabilities of the hardware on which
         // this code is running and based on the size of input.
+        DataDigester digester = new MultipleDigestDataDigester(mds);
         int dataSourceIndex = 0;
         for (DataSource input : contents) {
             long inputOffset = 0;
@@ -508,7 +471,7 @@
                     mds[i].update(chunkContentPrefix);
                 }
                 try {
-                    input.feedIntoMessageDigests(mds, inputOffset, chunkSize);
+                    input.feedIntoDataDigester(digester, inputOffset, chunkSize);
                 } catch (IOException e) {
                     throw new DigestException(
                             "Failed to digest chunk #" + chunkIndex + " of section #"
@@ -967,155 +930,26 @@
     }
 
     /**
-     * Source of data to be digested.
+     * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
      */
-    private static interface DataSource {
+    private static class MultipleDigestDataDigester implements DataDigester {
+        private final MessageDigest[] mMds;
 
-        /**
-         * Returns the size (in bytes) of the data offered by this source.
-         */
-        long size();
-
-        /**
-         * Feeds the specified region of this source's data into the provided digests. Each digest
-         * instance gets the same data.
-         *
-         * @param offset offset of the region inside this data source.
-         * @param size size (in bytes) of the region.
-         */
-        void feedIntoMessageDigests(MessageDigest[] mds, long offset, int size) throws IOException;
-    }
-
-    /**
-     * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
-     * of the file requested by
-     * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
-     */
-    private static final class MemoryMappedFileDataSource implements DataSource {
-        private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
-
-        private final FileDescriptor mFd;
-        private final long mFilePosition;
-        private final long mSize;
-
-        /**
-         * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
-         *
-         * @param position start position of the region in the file.
-         * @param size size (in bytes) of the region.
-         */
-        public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
-            mFd = fd;
-            mFilePosition = position;
-            mSize = size;
+        MultipleDigestDataDigester(MessageDigest[] mds) {
+            mMds = mds;
         }
 
         @Override
-        public long size() {
-            return mSize;
-        }
-
-        @Override
-        public void feedIntoMessageDigests(
-                MessageDigest[] mds, long offset, int size) throws IOException {
-            // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
-            // method was settled on a straightforward mmap with prefaulting.
-            //
-            // This method is not using FileChannel.map API because that API does not offset a way
-            // to "prefault" the resulting memory pages. Without prefaulting, performance is about
-            // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
-            // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
-            // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
-            // time, which is not compensated for by faster reads.
-
-            // We mmap the smallest region of the file containing the requested data. mmap requires
-            // that the start offset in the file must be a multiple of memory page size. We thus may
-            // need to mmap from an offset less than the requested offset.
-            long filePosition = mFilePosition + offset;
-            long mmapFilePosition =
-                    (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
-            int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
-            long mmapRegionSize = size + dataStartOffsetInMmapRegion;
-            long mmapPtr = 0;
-            try {
-                mmapPtr = Os.mmap(
-                        0, // let the OS choose the start address of the region in memory
-                        mmapRegionSize,
-                        OsConstants.PROT_READ,
-                        OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
-                        mFd,
-                        mmapFilePosition);
-                // Feeding a memory region into MessageDigest requires the region to be represented
-                // as a direct ByteBuffer.
-                ByteBuffer buf = new DirectByteBuffer(
-                        size,
-                        mmapPtr + dataStartOffsetInMmapRegion,
-                        mFd,  // not really needed, but just in case
-                        null, // no need to clean up -- it's taken care of by the finally block
-                        true  // read only buffer
-                        );
-                for (MessageDigest md : mds) {
-                    buf.position(0);
-                    md.update(buf);
-                }
-            } catch (ErrnoException e) {
-                throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
-            } finally {
-                if (mmapPtr != 0) {
-                    try {
-                        Os.munmap(mmapPtr, mmapRegionSize);
-                    } catch (ErrnoException ignored) {}
-                }
+        public void consume(ByteBuffer buffer) {
+            buffer = buffer.slice();
+            for (MessageDigest md : mMds) {
+                buffer.position(0);
+                md.update(buffer);
             }
         }
-    }
-
-    /**
-     * {@link DataSource} which provides data from a {@link ByteBuffer}.
-     */
-    private static final class ByteBufferDataSource implements DataSource {
-        /**
-         * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
-         * The buffer's position is 0 and limit is equal to capacity.
-         */
-        private final ByteBuffer mBuf;
-
-        public ByteBufferDataSource(ByteBuffer buf) {
-            // Defensive copy, to avoid changes to mBuf being visible in buf.
-            mBuf = buf.slice();
-        }
 
         @Override
-        public long size() {
-            return mBuf.capacity();
-        }
-
-        @Override
-        public void feedIntoMessageDigests(
-                MessageDigest[] mds, long offset, int size) throws IOException {
-            // There's no way to tell MessageDigest to read data from ByteBuffer from a position
-            // other than the buffer's current position. We thus need to change the buffer's
-            // position to match the requested offset.
-            //
-            // In the future, it may be necessary to compute digests of multiple regions in
-            // parallel. Given that digest computation is a slow operation, we enable multiple
-            // such requests to be fulfilled by this instance. This is achieved by serially
-            // creating a new ByteBuffer corresponding to the requested data range and then,
-            // potentially concurrently, feeding these buffers into MessageDigest instances.
-            ByteBuffer region;
-            synchronized (mBuf) {
-                mBuf.position((int) offset);
-                mBuf.limit((int) offset + size);
-                region = mBuf.slice();
-            }
-
-            for (MessageDigest md : mds) {
-                // Need to reset position to 0 at the start of each iteration because
-                // MessageDigest.update below sets it to the buffer's limit.
-                region.position(0);
-                md.update(region);
-            }
-        }
+        public void finish() {}
     }
 
     /**
diff --git a/android/util/apk/ApkVerityBuilder.java b/android/util/apk/ApkVerityBuilder.java
new file mode 100644
index 0000000..7412ef4
--- /dev/null
+++ b/android/util/apk/ApkVerityBuilder.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+
+/**
+ * ApkVerityBuilder builds the APK verity tree and the verity header, which will be used by the
+ * kernel to verity the APK content on access.
+ *
+ * <p>Unlike a regular Merkle tree, APK verity tree does not cover the content fully. Due to
+ * the existing APK format, it has to skip APK Signing Block and also has some special treatment for
+ * the "Central Directory offset" field of ZIP End of Central Directory.
+ *
+ * @hide
+ */
+abstract class ApkVerityBuilder {
+    private ApkVerityBuilder() {}
+
+    private static final int CHUNK_SIZE_BYTES = 4096;  // Typical Linux block size
+    private static final int DIGEST_SIZE_BYTES = 32;  // SHA-256 size
+    private static final int FSVERITY_HEADER_SIZE_BYTES = 64;
+    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE = 4;
+    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+    private static final String JCA_DIGEST_ALGORITHM = "SHA-256";
+    private static final byte[] DEFAULT_SALT = new byte[8];
+
+    static class ApkVerityResult {
+        public final ByteBuffer fsverityData;
+        public final byte[] rootHash;
+
+        ApkVerityResult(ByteBuffer fsverityData, byte[] rootHash) {
+            this.fsverityData = fsverityData;
+            this.rootHash = rootHash;
+        }
+    }
+
+    /**
+     * Generates fsverity metadata and the Merkle tree into the {@link ByteBuffer} created by the
+     * {@link ByteBufferFactory}. The bytes layout in the buffer will be used by the kernel and is
+     * ready to be appended to the target file to set up fsverity. For fsverity to work, this data
+     * must be placed at the next page boundary, and the caller must add additional padding in that
+     * case.
+     *
+     * @return ApkVerityResult containing the fsverity data and the root hash of the Merkle tree.
+     */
+    static ApkVerityResult generateApkVerity(RandomAccessFile apk,
+            SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
+            throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
+        assertSigningBlockAlignedAndHasFullPages(signatureInfo);
+
+        long signingBlockSize =
+                signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+        long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+        int[] levelOffset = calculateVerityLevelOffset(dataSize);
+        ByteBuffer output = bufferFactory.create(
+                CHUNK_SIZE_BYTES +  // fsverity header + extensions + padding
+                levelOffset[levelOffset.length - 1] +  // Merkle tree size
+                FSVERITY_HEADER_SIZE_BYTES);  // second fsverity header (verbatim copy)
+
+        // Start generating the tree from the block boundary as the kernel will expect.
+        ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES,
+                output.limit() - FSVERITY_HEADER_SIZE_BYTES);
+        byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset,
+                treeOutput);
+
+        ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT);
+        output.put(integrityHeader);
+        output.put(generateFsverityExtensions());
+
+        integrityHeader.rewind();
+        output.put(integrityHeader);
+        output.rewind();
+        return new ApkVerityResult(output, rootHash);
+    }
+
+    /**
+     * A helper class to consume and digest data by block continuously, and write into a buffer.
+     */
+    private static class BufferedDigester implements DataDigester {
+        /** Amount of the data to digest in each cycle before writting out the digest. */
+        private static final int BUFFER_SIZE = CHUNK_SIZE_BYTES;
+
+        /**
+         * Amount of data the {@link MessageDigest} has consumed since the last reset. This must be
+         * always less than BUFFER_SIZE since {@link MessageDigest} is reset whenever it has
+         * consumed BUFFER_SIZE of data.
+         */
+        private int mBytesDigestedSinceReset;
+
+        /** The final output {@link ByteBuffer} to write the digest to sequentially. */
+        private final ByteBuffer mOutput;
+
+        private final MessageDigest mMd;
+        private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES];
+        private final byte[] mSalt;
+
+        private BufferedDigester(byte[] salt, ByteBuffer output) throws NoSuchAlgorithmException {
+            mSalt = salt;
+            mOutput = output.slice();
+            mMd = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+            mMd.update(mSalt);
+            mBytesDigestedSinceReset = 0;
+        }
+
+        /**
+         * Consumes and digests data up to BUFFER_SIZE (may continue from the previous remaining),
+         * then writes the final digest to the output buffer.  Repeat until all data are consumed.
+         * If the last consumption is not enough for BUFFER_SIZE, the state will stay and future
+         * consumption will continuous from there.
+         */
+        @Override
+        public void consume(ByteBuffer buffer) throws DigestException {
+            int offset = buffer.position();
+            int remaining = buffer.remaining();
+            while (remaining > 0) {
+                int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset);
+                // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object.
+                buffer.limit(buffer.position() + allowance);
+                mMd.update(buffer);
+                offset += allowance;
+                remaining -= allowance;
+                mBytesDigestedSinceReset += allowance;
+
+                if (mBytesDigestedSinceReset == BUFFER_SIZE) {
+                    mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+                    mOutput.put(mDigestBuffer);
+                    // After digest, MessageDigest resets automatically, so no need to reset again.
+                    mMd.update(mSalt);
+                    mBytesDigestedSinceReset = 0;
+                }
+            }
+        }
+
+        /** Finish the current digestion if any. */
+        @Override
+        public void finish() throws DigestException {
+            if (mBytesDigestedSinceReset == 0) {
+                return;
+            }
+            mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+            mOutput.put(mDigestBuffer);
+        }
+
+        private void fillUpLastOutputChunk() {
+            int extra = (int) (BUFFER_SIZE - mOutput.position() % BUFFER_SIZE);
+            if (extra == 0) {
+                return;
+            }
+            mOutput.put(ByteBuffer.allocate(extra));
+        }
+    }
+
+    /**
+     * Digest the source by chunk in the given range.  If the last chunk is not a full chunk,
+     * digest the remaining.
+     */
+    private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize)
+            throws IOException, DigestException {
+        long inputRemaining = source.size();
+        long inputOffset = 0;
+        while (inputRemaining > 0) {
+            int size = (int) Math.min(inputRemaining, chunkSize);
+            source.feedIntoDataDigester(digester, inputOffset, size);
+            inputOffset += size;
+            inputRemaining -= size;
+        }
+    }
+
+    // Rationale: 1) 1 MB should fit in memory space on all devices. 2) It is not too granular
+    // thus the syscall overhead is not too big.
+    private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024;
+
+    private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk,
+            SignatureInfo signatureInfo, byte[] salt, ByteBuffer output)
+            throws IOException, NoSuchAlgorithmException, DigestException {
+        BufferedDigester digester = new BufferedDigester(salt, output);
+
+        // 1. Digest from the beginning of the file, until APK Signing Block is reached.
+        consumeByChunk(digester,
+                new MemoryMappedFileDataSource(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset),
+                MMAP_REGION_SIZE_BYTES);
+
+        // 2. Skip APK Signing Block and continue digesting, until the Central Directory offset
+        // field in EoCD is reached.
+        long eocdCdOffsetFieldPosition =
+                signatureInfo.eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET;
+        consumeByChunk(digester,
+                new MemoryMappedFileDataSource(apk.getFD(), signatureInfo.centralDirOffset,
+                    eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset),
+                MMAP_REGION_SIZE_BYTES);
+
+        // 3. Fill up the rest of buffer with 0s.
+        ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate(
+                ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+        alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset));
+        alternativeCentralDirOffset.flip();
+        digester.consume(alternativeCentralDirOffset);
+
+        // 4. Read from end of the Central Directory offset field in EoCD to the end of the file.
+        long offsetAfterEocdCdOffsetField =
+                eocdCdOffsetFieldPosition + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+        consumeByChunk(digester,
+                new MemoryMappedFileDataSource(apk.getFD(), offsetAfterEocdCdOffsetField,
+                    apk.length() - offsetAfterEocdCdOffsetField),
+                MMAP_REGION_SIZE_BYTES);
+        digester.finish();
+
+        // 5. Fill up the rest of buffer with 0s.
+        digester.fillUpLastOutputChunk();
+    }
+
+    private static byte[] generateApkVerityTree(RandomAccessFile apk, SignatureInfo signatureInfo,
+            byte[] salt, int[] levelOffset, ByteBuffer output)
+            throws IOException, NoSuchAlgorithmException, DigestException {
+        // 1. Digest the apk to generate the leaf level hashes.
+        generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
+                    levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
+
+        // 2. Digest the lower level hashes bottom up.
+        for (int level = levelOffset.length - 3; level >= 0; level--) {
+            ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]);
+            ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]);
+
+            DataSource source = new ByteBufferDataSource(inputBuffer);
+            BufferedDigester digester = new BufferedDigester(salt, outputBuffer);
+            consumeByChunk(digester, source, CHUNK_SIZE_BYTES);
+            digester.finish();
+
+            digester.fillUpLastOutputChunk();
+        }
+
+        // 3. Digest the first block (i.e. first level) to generate the root hash.
+        byte[] rootHash = new byte[DIGEST_SIZE_BYTES];
+        BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash));
+        digester.consume(slice(output, 0, CHUNK_SIZE_BYTES));
+        digester.finish();
+        return rootHash;
+    }
+
+    private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) {
+        if (salt.length != 8) {
+            throw new IllegalArgumentException("salt is not 8 bytes long");
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        // TODO(b/30972906): insert a reference when there is a public one.
+        buffer.put("TrueBrew".getBytes());  // magic
+        buffer.put((byte) 1);        // major version
+        buffer.put((byte) 0);        // minor version
+        buffer.put((byte) 12);       // log2(block-size) == log2(4096)
+        buffer.put((byte) 7);        // log2(leaves-per-node) == log2(block-size / digest-size)
+                                     //                       == log2(4096 / 32)
+        buffer.putShort((short) 1);  // meta algorithm, 1: SHA-256 FIXME finalize constant
+        buffer.putShort((short) 1);  // data algorithm, 1: SHA-256 FIXME finalize constant
+        buffer.putInt(0x1);          // flags, 0x1: has extension, FIXME also hide it
+        buffer.putInt(0);            // reserved
+        buffer.putLong(fileSize);    // original i_size
+        buffer.put(salt);            // salt (8 bytes)
+
+        // TODO(b/30972906): Add extension.
+
+        buffer.rewind();
+        return buffer;
+    }
+
+    private static ByteBuffer generateFsverityExtensions() {
+        return ByteBuffer.allocate(64); // TODO(b/30972906): implement this.
+    }
+
+    /**
+     * Returns an array of summed area table of level size in the verity tree.  In other words, the
+     * returned array is offset of each level in the verity tree file format, plus an additional
+     * offset of the next non-existing level (i.e. end of the last level + 1).  Thus the array size
+     * is level + 1.  Thus, the returned array is guarantee to have at least 2 elements.
+     */
+    private static int[] calculateVerityLevelOffset(long fileSize) {
+        ArrayList<Long> levelSize = new ArrayList<>();
+        while (true) {
+            long levelDigestSize = divideRoundup(fileSize, CHUNK_SIZE_BYTES) * DIGEST_SIZE_BYTES;
+            long chunksSize = CHUNK_SIZE_BYTES * divideRoundup(levelDigestSize, CHUNK_SIZE_BYTES);
+            levelSize.add(chunksSize);
+            if (levelDigestSize <= CHUNK_SIZE_BYTES) {
+                break;
+            }
+            fileSize = levelDigestSize;
+        }
+
+        // Reverse and convert to summed area table.
+        int[] levelOffset = new int[levelSize.size() + 1];
+        levelOffset[0] = 0;
+        for (int i = 0; i < levelSize.size(); i++) {
+            // We don't support verity tree if it is larger then Integer.MAX_VALUE.
+            levelOffset[i + 1] = levelOffset[i]
+                    + Math.toIntExact(levelSize.get(levelSize.size() - i - 1));
+        }
+        return levelOffset;
+    }
+
+    private static void assertSigningBlockAlignedAndHasFullPages(SignatureInfo signatureInfo) {
+        if (signatureInfo.apkSigningBlockOffset % CHUNK_SIZE_BYTES != 0) {
+            throw new IllegalArgumentException(
+                    "APK Signing Block does not start at the page  boundary: "
+                    + signatureInfo.apkSigningBlockOffset);
+        }
+
+        if ((signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset)
+                % CHUNK_SIZE_BYTES != 0) {
+            throw new IllegalArgumentException(
+                    "Size of APK Signing Block is not a multiple of 4096: "
+                    + (signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset));
+        }
+    }
+
+    /** Returns a slice of the buffer which shares content with the provided buffer. */
+    private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) {
+        ByteBuffer b = buffer.duplicate();
+        b.position(0);  // to ensure position <= limit invariant.
+        b.limit(end);
+        b.position(begin);
+        return b.slice();
+    }
+
+    /** Divides a number and round up to the closest integer. */
+    private static long divideRoundup(long dividend, long divisor) {
+        return (dividend + divisor - 1) / divisor;
+    }
+}
diff --git a/android/util/apk/ByteBufferDataSource.java b/android/util/apk/ByteBufferDataSource.java
new file mode 100644
index 0000000..3976568
--- /dev/null
+++ b/android/util/apk/ByteBufferDataSource.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.util.apk;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a {@link ByteBuffer}.
+ */
+class ByteBufferDataSource implements DataSource {
+    /**
+     * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
+     * The buffer's position is 0 and limit is equal to capacity.
+     */
+    private final ByteBuffer mBuf;
+
+    ByteBufferDataSource(ByteBuffer buf) {
+        // Defensive copy, to avoid changes to mBuf being visible in buf, and to ensure position is
+        // 0 and limit == capacity.
+        mBuf = buf.slice();
+    }
+
+    @Override
+    public long size() {
+        return mBuf.capacity();
+    }
+
+    @Override
+    public void feedIntoDataDigester(DataDigester md, long offset, int size)
+            throws IOException, DigestException {
+        // There's no way to tell MessageDigest to read data from ByteBuffer from a position
+        // other than the buffer's current position. We thus need to change the buffer's
+        // position to match the requested offset.
+        //
+        // In the future, it may be necessary to compute digests of multiple regions in
+        // parallel. Given that digest computation is a slow operation, we enable multiple
+        // such requests to be fulfilled by this instance. This is achieved by serially
+        // creating a new ByteBuffer corresponding to the requested data range and then,
+        // potentially concurrently, feeding these buffers into MessageDigest instances.
+        ByteBuffer region;
+        synchronized (mBuf) {
+            mBuf.position(0);
+            mBuf.limit((int) offset + size);
+            mBuf.position((int) offset);
+            region = mBuf.slice();
+        }
+
+        md.consume(region);
+    }
+}
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/util/apk/ByteBufferFactory.java
similarity index 68%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to android/util/apk/ByteBufferFactory.java
index e28e1b3..7a99882 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/util/apk/ByteBufferFactory.java
@@ -11,16 +11,18 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package android.telephony.ims.feature;
+package android.util.apk;
+
+import java.nio.ByteBuffer;
 
 /**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
+ * Provider of {@link ByteBuffer} instances.
  * @hide
  */
-
-public interface IRcsFeature {
+public interface ByteBufferFactory {
+    /** Initiates a {@link ByteBuffer} with the given size. */
+    ByteBuffer create(int capacity);
 }
diff --git a/android/util/apk/DataDigester.java b/android/util/apk/DataDigester.java
new file mode 100644
index 0000000..278be80
--- /dev/null
+++ b/android/util/apk/DataDigester.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.util.apk;
+
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+interface DataDigester {
+    /** Consumes the {@link ByteBuffer}. */
+    void consume(ByteBuffer buffer) throws DigestException;
+
+    /** Finishes the digestion. Must be called after the last {@link #consume(ByteBuffer)}. */
+    void finish() throws DigestException;
+}
diff --git a/android/util/apk/DataSource.java b/android/util/apk/DataSource.java
new file mode 100644
index 0000000..82f3800
--- /dev/null
+++ b/android/util/apk/DataSource.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.util.apk;
+
+import java.io.IOException;
+import java.security.DigestException;
+
+/** Source of data to be digested. */
+interface DataSource {
+
+    /**
+     * Returns the size (in bytes) of the data offered by this source.
+     */
+    long size();
+
+    /**
+     * Feeds the specified region of this source's data into the provided digester.
+     *
+     * @param offset offset of the region inside this data source.
+     * @param size size (in bytes) of the region.
+     */
+    void feedIntoDataDigester(DataDigester md, long offset, int size)
+            throws IOException, DigestException;
+}
diff --git a/android/util/apk/MemoryMappedFileDataSource.java b/android/util/apk/MemoryMappedFileDataSource.java
new file mode 100644
index 0000000..8d2b1e3
--- /dev/null
+++ b/android/util/apk/MemoryMappedFileDataSource.java
@@ -0,0 +1,105 @@
+/*
+ * 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.apk;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.DirectByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
+ * of the file.
+ */
+class MemoryMappedFileDataSource implements DataSource {
+    private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
+
+    private final FileDescriptor mFd;
+    private final long mFilePosition;
+    private final long mSize;
+
+    /**
+     * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
+     *
+     * @param position start position of the region in the file.
+     * @param size size (in bytes) of the region.
+     */
+    MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
+        mFd = fd;
+        mFilePosition = position;
+        mSize = size;
+    }
+
+    @Override
+    public long size() {
+        return mSize;
+    }
+
+    @Override
+    public void feedIntoDataDigester(DataDigester md, long offset, int size)
+            throws IOException, DigestException {
+        // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
+        // method was settled on a straightforward mmap with prefaulting.
+        //
+        // This method is not using FileChannel.map API because that API does not offset a way
+        // to "prefault" the resulting memory pages. Without prefaulting, performance is about
+        // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
+        // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
+        // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
+        // time, which is not compensated for by faster reads.
+
+        // We mmap the smallest region of the file containing the requested data. mmap requires
+        // that the start offset in the file must be a multiple of memory page size. We thus may
+        // need to mmap from an offset less than the requested offset.
+        long filePosition = mFilePosition + offset;
+        long mmapFilePosition =
+                (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
+        int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
+        long mmapRegionSize = size + dataStartOffsetInMmapRegion;
+        long mmapPtr = 0;
+        try {
+            mmapPtr = Os.mmap(
+                    0, // let the OS choose the start address of the region in memory
+                    mmapRegionSize,
+                    OsConstants.PROT_READ,
+                    OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
+                    mFd,
+                    mmapFilePosition);
+            ByteBuffer buf = new DirectByteBuffer(
+                    size,
+                    mmapPtr + dataStartOffsetInMmapRegion,
+                    mFd,  // not really needed, but just in case
+                    null, // no need to clean up -- it's taken care of by the finally block
+                    true  // read only buffer
+                    );
+            md.consume(buf);
+        } catch (ErrnoException e) {
+            throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
+        } finally {
+            if (mmapPtr != 0) {
+                try {
+                    Os.munmap(mmapPtr, mmapRegionSize);
+                } catch (ErrnoException ignored) { }
+            }
+        }
+    }
+}
diff --git a/android/util/apk/SignatureInfo.java b/android/util/apk/SignatureInfo.java
new file mode 100644
index 0000000..8e1233a
--- /dev/null
+++ b/android/util/apk/SignatureInfo.java
@@ -0,0 +1,49 @@
+/*
+ * 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.apk;
+
+import java.nio.ByteBuffer;
+
+/**
+ * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
+ * contained in the block against the file.
+ */
+class SignatureInfo {
+    /** Contents of APK Signature Scheme v2 block. */
+    public final ByteBuffer signatureBlock;
+
+    /** Position of the APK Signing Block in the file. */
+    public final long apkSigningBlockOffset;
+
+    /** Position of the ZIP Central Directory in the file. */
+    public final long centralDirOffset;
+
+    /** Position of the ZIP End of Central Directory (EoCD) in the file. */
+    public final long eocdOffset;
+
+    /** Contents of ZIP End of Central Directory (EoCD) of the file. */
+    public final ByteBuffer eocd;
+
+    SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset,
+            long eocdOffset, ByteBuffer eocd) {
+        this.signatureBlock = signatureBlock;
+        this.apkSigningBlockOffset = apkSigningBlockOffset;
+        this.centralDirOffset = centralDirOffset;
+        this.eocdOffset = eocdOffset;
+        this.eocd = eocd;
+    }
+}
diff --git a/android/util/proto/ProtoOutputStream.java b/android/util/proto/ProtoOutputStream.java
index 43a9789..a94806a 100644
--- a/android/util/proto/ProtoOutputStream.java
+++ b/android/util/proto/ProtoOutputStream.java
@@ -127,42 +127,48 @@
 
     public static final long FIELD_TYPE_UNKNOWN = 0;
 
+    /**
+     * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+     * so no extra mapping needs to be maintained in this case.
+     */
     public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
     public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+//  public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+    public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
 
     private static final String[] FIELD_TYPE_NAMES = new String[] {
         "Double",
         "Float",
-        "Int32",
         "Int64",
-        "UInt32",
         "UInt64",
-        "SInt32",
-        "SInt64",
-        "Fixed32",
+        "Int32",
         "Fixed64",
-        "SFixed32",
-        "SFixed64",
+        "Fixed32",
         "Bool",
         "String",
+        "Group",  // This field is deprecated but reserved here for indexing.
+        "Message",
         "Bytes",
+        "UInt32",
         "Enum",
-        "Object",
+        "SFixed32",
+        "SFixed64",
+        "SInt32",
+        "SInt64",
     };
 
     //
@@ -867,21 +873,21 @@
         assertNotCompacted();
         final int id = (int)fieldId;
 
-        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+        switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
             // bytes
-            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
                 writeBytesImpl(id, val);
                 break;
-            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
-            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
                 writeRepeatedBytesImpl(id, val);
                 break;
             // Object
-            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
                 writeObjectImpl(id, val);
                 break;
-            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
-            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
                 writeRepeatedObjectImpl(id, val);
                 break;
             // nothing else allowed
@@ -899,7 +905,7 @@
         assertNotCompacted();
         final int id = (int)fieldId;
 
-        if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) {
+        if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) {
             final long count = fieldId & FIELD_COUNT_MASK;
             if (count == FIELD_COUNT_SINGLE) {
                 return startObjectImpl(id, false);
@@ -2091,7 +2097,7 @@
     @Deprecated
     public long startObject(long fieldId) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
 
         return startObjectImpl(id, false);
     }
@@ -2119,7 +2125,7 @@
     @Deprecated
     public long startRepeatedObject(long fieldId) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
 
         return startObjectImpl(id, true);
     }
@@ -2217,7 +2223,7 @@
     @Deprecated
     public void writeObject(long fieldId, byte[] value) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
 
         writeObjectImpl(id, value);
     }
@@ -2237,7 +2243,7 @@
     @Deprecated
     public void writeRepeatedObject(long fieldId, byte[] value) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
 
         writeRepeatedObjectImpl(id, value);
     }
@@ -2296,7 +2302,7 @@
             final String typeString = getFieldTypeString(fieldType);
             if (typeString != null && countString != null) {
                 final StringBuilder sb = new StringBuilder();
-                if (expectedType == FIELD_TYPE_OBJECT) {
+                if (expectedType == FIELD_TYPE_MESSAGE) {
                     sb.append("start");
                 } else {
                     sb.append("write");
@@ -2306,7 +2312,7 @@
                 sb.append(" called for field ");
                 sb.append((int)fieldId);
                 sb.append(" which should be used with ");
-                if (fieldType == FIELD_TYPE_OBJECT) {
+                if (fieldType == FIELD_TYPE_MESSAGE) {
                     sb.append("start");
                 } else {
                     sb.append("write");
@@ -2321,7 +2327,7 @@
                 throw new IllegalArgumentException(sb.toString());
             } else {
                 final StringBuilder sb = new StringBuilder();
-                if (expectedType == FIELD_TYPE_OBJECT) {
+                if (expectedType == FIELD_TYPE_MESSAGE) {
                     sb.append("start");
                 } else {
                     sb.append("write");
diff --git a/android/view/Display.java b/android/view/Display.java
index e7c3f92..6a44cdb 100644
--- a/android/view/Display.java
+++ b/android/view/Display.java
@@ -294,11 +294,10 @@
 
     /**
      * Display state: The display is dozing in a suspended low power state; it is still
-     * on but is optimized for showing static system-provided content while the device
-     * is non-interactive.  This mode may be used to conserve even more power by allowing
-     * the hardware to stop applying frame buffer updates from the graphics subsystem or
-     * to take over the display and manage it autonomously to implement low power always-on
-     * display functionality.
+     * on but the CPU is not updating it. This may be used in one of two ways: to show
+     * static system-provided content while the device is non-interactive, or to allow
+     * a "Sidekick" compute resource to update the display. For this reason, the
+     * CPU must not control the display in this mode.
      *
      * @see #getState
      * @see android.os.PowerManager#isInteractive
@@ -313,6 +312,18 @@
      */
     public static final int STATE_VR = 5;
 
+    /**
+     * Display state: The display is in a suspended full power state; it is still
+     * on but the CPU is not updating it. This may be used in one of two ways: to show
+     * static system-provided content while the device is non-interactive, or to allow
+     * a "Sidekick" compute resource to update the display. For this reason, the
+     * CPU must not control the display in this mode.
+     *
+     * @see #getState
+     * @see android.os.PowerManager#isInteractive
+     */
+    public static final int STATE_ON_SUSPEND = 6;
+
     /* The color mode constants defined below must be kept in sync with the ones in
      * system/core/include/system/graphics-base.h */
 
@@ -994,7 +1005,7 @@
      * Gets the state of the display, such as whether it is on or off.
      *
      * @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
-     * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, or
+     * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, {@link #STATE_ON_SUSPEND}, or
      * {@link #STATE_UNKNOWN}.
      */
     public int getState() {
@@ -1113,6 +1124,8 @@
                 return "DOZE_SUSPEND";
             case STATE_VR:
                 return "VR";
+            case STATE_ON_SUSPEND:
+                return "ON_SUSPEND";
             default:
                 return Integer.toString(state);
         }
@@ -1120,11 +1133,11 @@
 
     /**
      * Returns true if display updates may be suspended while in the specified
-     * display power state.
+     * display power state. In SUSPEND states, updates are absolutely forbidden.
      * @hide
      */
     public static boolean isSuspendedState(int state) {
-        return state == STATE_OFF || state == STATE_DOZE_SUSPEND;
+        return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND;
     }
 
     /**
diff --git a/android/view/DisplayFrames.java b/android/view/DisplayFrames.java
new file mode 100644
index 0000000..e6861d8
--- /dev/null
+++ b/android/view/DisplayFrames.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+
+/**
+ * Container class for all the display frames that affect how we do window layout on a display.
+ * @hide
+ */
+public class DisplayFrames {
+    public final int mDisplayId;
+
+    /**
+     * The current size of the screen; really; extends into the overscan area of the screen and
+     * doesn't account for any system elements like the status bar.
+     */
+    public final Rect mOverscan = new Rect();
+
+    /**
+     * The current visible size of the screen; really; (ir)regardless of whether the status bar can
+     * be hidden but not extending into the overscan area.
+     */
+    public final Rect mUnrestricted = new Rect();
+
+    /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */
+    public final Rect mRestrictedOverscan = new Rect();
+
+    /**
+     * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar
+     * can't be hidden; in that case it effectively carves out that area of the display from all
+     * other windows.
+     */
+    public final Rect mRestricted = new Rect();
+
+    /**
+     * During layout, the current screen borders accounting for any currently visible system UI
+     * elements.
+     */
+    public final Rect mSystem = new Rect();
+
+    /** For applications requesting stable content insets, these are them. */
+    public final Rect mStable = new Rect();
+
+    /**
+     * For applications requesting stable content insets but have also set the fullscreen window
+     * flag, these are the stable dimensions without the status bar.
+     */
+    public final Rect mStableFullscreen = new Rect();
+
+    /**
+     * During layout, the current screen borders with all outer decoration (status bar, input method
+     * dock) accounted for.
+     */
+    public final Rect mCurrent = new Rect();
+
+    /**
+     * During layout, the frame in which content should be displayed to the user, accounting for all
+     * screen decoration except for any space they deem as available for other content. This is
+     * usually the same as mCurrent*, but may be larger if the screen decor has supplied content
+     * insets.
+     */
+    public final Rect mContent = new Rect();
+
+    /**
+     * During layout, the frame in which voice content should be displayed to the user, accounting
+     * for all screen decoration except for any space they deem as available for other content.
+     */
+    public final Rect mVoiceContent = new Rect();
+
+    /** During layout, the current screen borders along which input method windows are placed. */
+    public final Rect mDock = new Rect();
+
+    private final Rect mDisplayInfoOverscan = new Rect();
+    private final Rect mRotatedDisplayInfoOverscan = new Rect();
+    public int mDisplayWidth;
+    public int mDisplayHeight;
+
+    public int mRotation;
+
+    public DisplayFrames(int displayId, DisplayInfo info) {
+        mDisplayId = displayId;
+        onDisplayInfoUpdated(info);
+    }
+
+    public void onDisplayInfoUpdated(DisplayInfo info) {
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
+        mRotation = info.rotation;
+        mDisplayInfoOverscan.set(
+                info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
+    }
+
+    public void onBeginLayout() {
+        switch (mRotation) {
+            case ROTATION_90:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.right;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.left;
+                break;
+            case ROTATION_180:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.right;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.left;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.top;
+                break;
+            case ROTATION_270:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.left;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.top;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.right;
+                break;
+            default:
+                mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan);
+                break;
+        }
+
+        mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight);
+        mOverscan.set(mRestrictedOverscan);
+        mSystem.set(mRestrictedOverscan);
+        mUnrestricted.set(mRotatedDisplayInfoOverscan);
+        mUnrestricted.right = mDisplayWidth - mUnrestricted.right;
+        mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom;
+        mRestricted.set(mUnrestricted);
+        mDock.set(mUnrestricted);
+        mContent.set(mUnrestricted);
+        mVoiceContent.set(mUnrestricted);
+        mStable.set(mUnrestricted);
+        mStableFullscreen.set(mUnrestricted);
+        mCurrent.set(mUnrestricted);
+
+    }
+
+    public int getInputMethodWindowVisibleHeight() {
+        return mDock.bottom - mCurrent.bottom;
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        mStable.writeToProto(proto, STABLE_BOUNDS);
+        proto.end(token);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
+                + " r=" + mRotation);
+        final String myPrefix = prefix + "  ";
+        dumpFrame(mStable, "mStable", myPrefix, pw);
+        dumpFrame(mStableFullscreen, "mStableFullscreen", myPrefix, pw);
+        dumpFrame(mDock, "mDock", myPrefix, pw);
+        dumpFrame(mCurrent, "mCurrent", myPrefix, pw);
+        dumpFrame(mSystem, "mSystem", myPrefix, pw);
+        dumpFrame(mContent, "mContent", myPrefix, pw);
+        dumpFrame(mVoiceContent, "mVoiceContent", myPrefix, pw);
+        dumpFrame(mOverscan, "mOverscan", myPrefix, pw);
+        dumpFrame(mRestrictedOverscan, "mRestrictedOverscan", myPrefix, pw);
+        dumpFrame(mRestricted, "mRestricted", myPrefix, pw);
+        dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
+        dumpFrame(mDisplayInfoOverscan, "mDisplayInfoOverscan", myPrefix, pw);
+        dumpFrame(mRotatedDisplayInfoOverscan, "mRotatedDisplayInfoOverscan", myPrefix, pw);
+    }
+
+    private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
+        pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
+    }
+}
diff --git a/android/view/FocusFinder.java b/android/view/FocusFinder.java
index 74555de..713cfb4 100644
--- a/android/view/FocusFinder.java
+++ b/android/view/FocusFinder.java
@@ -530,7 +530,7 @@
      * axis distances.  Warning: this fudge factor is finely tuned, be sure to
      * run all focus tests if you dare tweak it.
      */
-    int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+    long getWeightedDistanceFor(long majorAxisDistance, long minorAxisDistance) {
         return 13 * majorAxisDistance * majorAxisDistance
                 + minorAxisDistance * minorAxisDistance;
     }
diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java
index b34dfbf..6c006ca 100644
--- a/android/view/IWindowManagerImpl.java
+++ b/android/view/IWindowManagerImpl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.app.IAssistDataReceiver;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
@@ -29,7 +30,6 @@
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 
-import com.android.internal.app.IAssistScreenshotReceiver;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
@@ -261,7 +261,7 @@
     }
 
     @Override
-    public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
+    public boolean requestAssistScreenshot(IAssistDataReceiver receiver)
             throws RemoteException {
         // TODO Auto-generated method stub
         return false;
@@ -507,7 +507,7 @@
         throws RemoteException {}
 
     @Override
-    public void createInputConsumer(String name, InputChannel inputChannel)
+    public void createInputConsumer(IBinder token, String name, InputChannel inputChannel)
             throws RemoteException {}
 
     @Override
diff --git a/android/view/NotificationHeaderView.java b/android/view/NotificationHeaderView.java
index 5804560..ab0b3ee 100644
--- a/android/view/NotificationHeaderView.java
+++ b/android/view/NotificationHeaderView.java
@@ -20,6 +20,7 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Outline;
 import android.graphics.Rect;
@@ -43,6 +44,7 @@
     public static final int NO_COLOR = Notification.COLOR_INVALID;
     private final int mChildMinWidth;
     private final int mContentEndMargin;
+    private final int mGravity;
     private View mAppName;
     private View mHeaderText;
     private OnClickListener mExpandClickListener;
@@ -50,7 +52,6 @@
     private ImageView mExpandButton;
     private CachingIconView mIcon;
     private View mProfileBadge;
-    private View mInfo;
     private int mIconColor;
     private int mOriginalNotificationColor;
     private boolean mExpanded;
@@ -61,6 +62,7 @@
     private boolean mEntireHeaderClickable;
     private boolean mExpandOnlyOnButton;
     private boolean mAcceptAllTouches;
+    private int mTotalWidth;
 
     ViewOutlineProvider mProvider = new ViewOutlineProvider() {
         @Override
@@ -92,6 +94,11 @@
         mHeaderBackgroundHeight = res.getDimensionPixelSize(
                 R.dimen.notification_header_background_height);
         mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
+
+        int[] attrIds = { android.R.attr.gravity };
+        TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
+        mGravity = ta.getInt(0, 0);
+        ta.recycle();
     }
 
     @Override
@@ -146,6 +153,7 @@
                 mHeaderText.measure(childWidthSpec, wrapContentHeightSpec);
             }
         }
+        mTotalWidth = Math.min(totalWidth, givenWidth);
         setMeasuredDimension(givenWidth, givenHeight);
     }
 
@@ -153,6 +161,10 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int left = getPaddingStart();
         int end = getMeasuredWidth();
+        final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
+        if (centerAligned) {
+            left += getMeasuredWidth() / 2 - mTotalWidth / 2;
+        }
         int childCount = getChildCount();
         int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
         for (int i = 0; i < childCount; i++) {
diff --git a/android/view/RenderNode.java b/android/view/RenderNode.java
index ea6e63c..5070151 100644
--- a/android/view/RenderNode.java
+++ b/android/view/RenderNode.java
@@ -353,6 +353,11 @@
         return nHasShadow(mNativeRenderNode);
     }
 
+    /** setShadowColor */
+    public boolean setShadowColor(int color) {
+        return nSetShadowColor(mNativeRenderNode, color);
+    }
+
     /**
      * Enables or disables clipping to the outline.
      *
@@ -910,6 +915,8 @@
     @CriticalNative
     private static native boolean nHasShadow(long renderNode);
     @CriticalNative
+    private static native boolean nSetShadowColor(long renderNode, int color);
+    @CriticalNative
     private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
     @CriticalNative
     private static native boolean nSetRevealClip(long renderNode,
diff --git a/android/view/RenderNodeAnimator.java b/android/view/RenderNodeAnimator.java
index 9515040..c4a7160 100644
--- a/android/view/RenderNodeAnimator.java
+++ b/android/view/RenderNodeAnimator.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
 import android.graphics.Paint;
 import android.util.SparseIntArray;
@@ -281,12 +280,9 @@
         setTarget(mViewTarget.mRenderNode);
     }
 
-    public void setTarget(Canvas canvas) {
-        if (!(canvas instanceof DisplayListCanvas)) {
-            throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
-        }
-        final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
-        setTarget(recordingCanvas.mNode);
+    /** Sets the animation target to the owning view of the DisplayListCanvas */
+    public void setTarget(DisplayListCanvas canvas) {
+        setTarget(canvas.mNode);
     }
 
     private void setTarget(RenderNode node) {
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 6f8315a..5641009 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -295,6 +295,12 @@
     public static final int POWER_MODE_DOZE_SUSPEND = 3;
 
     /**
+     * Display power mode on: used while putting the screen into a suspended
+     * full power mode.  Use only with {@link SurfaceControl#setDisplayPowerMode}.
+     */
+    public static final int POWER_MODE_ON_SUSPEND = 4;
+
+    /**
      * A value for windowType used to indicate that the window should be omitted from screenshots
      * and display mirroring. A temporary workaround until we express such things with
      * the hierarchy.
@@ -1206,56 +1212,65 @@
         }
 
         public Transaction show(SurfaceControl sc) {
+            sc.checkNotReleased();
             nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
             return this;
         }
 
         public Transaction hide(SurfaceControl sc) {
+            sc.checkNotReleased();
             nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
             return this;
         }
 
         public Transaction setPosition(SurfaceControl sc, float x, float y) {
+            sc.checkNotReleased();
             nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
             return this;
         }
 
         public Transaction setSize(SurfaceControl sc, int w, int h) {
-            nativeSetSize(mNativeObject, sc.mNativeObject,
-                    w, h);
+            sc.checkNotReleased();
+            nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
             return this;
         }
 
         public Transaction setLayer(SurfaceControl sc, int z) {
+            sc.checkNotReleased();
             nativeSetLayer(mNativeObject, sc.mNativeObject, z);
             return this;
         }
 
         public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
+            sc.checkNotReleased();
             nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
                     relativeTo.getHandle(), z);
             return this;
         }
 
         public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+            sc.checkNotReleased();
             nativeSetTransparentRegionHint(mNativeObject,
                     sc.mNativeObject, transparentRegion);
             return this;
         }
 
         public Transaction setAlpha(SurfaceControl sc, float alpha) {
+            sc.checkNotReleased();
             nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
             return this;
         }
 
         public Transaction setMatrix(SurfaceControl sc,
                 float dsdx, float dtdx, float dtdy, float dsdy) {
+            sc.checkNotReleased();
             nativeSetMatrix(mNativeObject, sc.mNativeObject,
                     dsdx, dtdx, dtdy, dsdy);
             return this;
         }
 
         public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+            sc.checkNotReleased();
             if (crop != null) {
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
                         crop.left, crop.top, crop.right, crop.bottom);
@@ -1267,6 +1282,7 @@
         }
 
         public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
+            sc.checkNotReleased();
             if (crop != null) {
                 nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
                         crop.left, crop.top, crop.right, crop.bottom);
@@ -1278,40 +1294,48 @@
         }
 
         public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+            sc.checkNotReleased();
             nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
             return this;
         }
 
-        public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
+        public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
+                long frameNumber) {
+            sc.checkNotReleased();
             nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
             return this;
         }
 
         public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
                 long frameNumber) {
+            sc.checkNotReleased();
             nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
                     barrierSurface.mNativeObject, frameNumber);
             return this;
         }
 
         public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
+            sc.checkNotReleased();
             nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
             return this;
         }
 
         /** Re-parents a specific child layer to a new parent */
         public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+            sc.checkNotReleased();
             nativeReparent(mNativeObject, sc.mNativeObject,
                     newParentHandle);
             return this;
         }
 
         public Transaction detachChildren(SurfaceControl sc) {
+            sc.checkNotReleased();
             nativeSeverChildren(mNativeObject, sc.mNativeObject);
             return this;
         }
 
         public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
+            sc.checkNotReleased();
             nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
                     overrideScalingMode);
             return this;
@@ -1322,6 +1346,7 @@
          * @param color A float array with three values to represent r, g, b in range [0..1]
          */
         public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+            sc.checkNotReleased();
             nativeSetColor(mNativeObject, sc.mNativeObject, color);
             return this;
         }
@@ -1334,6 +1359,7 @@
          * (at which point the geometry influencing aspects of this transaction will then occur)
          */
         public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
+            sc.checkNotReleased();
             nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
             return this;
         }
@@ -1343,6 +1369,7 @@
          * Surface with the {@link #SECURE} flag.
          */
         Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+            sc.checkNotReleased();
             if (isSecure) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
             } else {
@@ -1356,6 +1383,7 @@
          * Surface with the {@link #OPAQUE} flag.
          */
         public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+            sc.checkNotReleased();
             if (isOpaque) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
             } else {
diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java
index 2166f6e..7c76bab 100644
--- a/android/view/ThreadedRenderer.java
+++ b/android/view/ThreadedRenderer.java
@@ -70,6 +70,7 @@
      * Name of the file that holds the shaders cache.
      */
     private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
+    private static final String CACHE_PATH_SKIASHADERS = "com.android.skia.shaders_cache";
 
     /**
      * System property used to enable or disable threaded rendering profiling.
@@ -272,7 +273,9 @@
      * @hide
      */
     public static void setupDiskCache(File cacheDir) {
-        ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+        ThreadedRenderer.setupShadersDiskCache(
+                new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath(),
+                new File(cacheDir, CACHE_PATH_SKIASHADERS).getAbsolutePath());
     }
 
     /**
@@ -1007,7 +1010,7 @@
     /** Not actually public - internal use only. This doc to make lint happy */
     public static native void disableVsync();
 
-    static native void setupShadersDiskCache(String cacheFile);
+    static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile);
 
     private static native void nRotateProcessStatsBuffer();
     private static native void nSetProcessStatsBuffer(int fd);
diff --git a/android/view/View.java b/android/view/View.java
index c043dca..be09fe8 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -14218,6 +14218,7 @@
      */
     public void setScaleX(float scaleX) {
         if (scaleX != getScaleX()) {
+            requireIsFinite(scaleX, "scaleX");
             invalidateViewProperty(true, false);
             mRenderNode.setScaleX(scaleX);
             invalidateViewProperty(false, true);
@@ -14254,6 +14255,7 @@
      */
     public void setScaleY(float scaleY) {
         if (scaleY != getScaleY()) {
+            requireIsFinite(scaleY, "scaleY");
             invalidateViewProperty(true, false);
             mRenderNode.setScaleY(scaleY);
             invalidateViewProperty(false, true);
@@ -14803,6 +14805,15 @@
         }
     }
 
+    private static void requireIsFinite(float transform, String propertyName) {
+        if (Float.isNaN(transform)) {
+            throw new IllegalArgumentException("Cannot set '" + propertyName + "' to Float.NaN");
+        }
+        if (Float.isInfinite(transform)) {
+            throw new IllegalArgumentException("Cannot set '" + propertyName + "' to infinity");
+        }
+    }
+
     /**
      * The visual x position of this view, in pixels. This is equivalent to the
      * {@link #setTranslationX(float) translationX} property plus the current
@@ -14889,6 +14900,7 @@
      */
     public void setElevation(float elevation) {
         if (elevation != getElevation()) {
+            requireIsFinite(elevation, "elevation");
             invalidateViewProperty(true, false);
             mRenderNode.setElevation(elevation);
             invalidateViewProperty(false, true);
@@ -14981,6 +14993,7 @@
      */
     public void setTranslationZ(float translationZ) {
         if (translationZ != getTranslationZ()) {
+            requireIsFinite(translationZ, "translationZ");
             invalidateViewProperty(true, false);
             mRenderNode.setTranslationZ(translationZ);
             invalidateViewProperty(false, true);
@@ -15169,6 +15182,15 @@
         return mRenderNode.hasShadow();
     }
 
+    /**
+     * @hide
+     */
+    public void setShadowColor(@ColorInt int color) {
+        if (mRenderNode.setShadowColor(color)) {
+            invalidateViewProperty(true, true);
+        }
+    }
+
 
     /** @hide */
     public void setRevealClip(boolean shouldClip, float x, float y, float radius) {
diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java
index 4500862..c44c8dd 100644
--- a/android/view/ViewConfiguration.java
+++ b/android/view/ViewConfiguration.java
@@ -92,7 +92,7 @@
      * Defines the duration in milliseconds a user needs to hold down the
      * appropriate button to enable the accessibility shortcut once it's configured.
      */
-    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000;
 
     /**
      * Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 37829f0..e30496f 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -513,7 +513,7 @@
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
         if (!sCompatibilityDone) {
-            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
+            sAlwaysAssignFocus = true;
 
             sCompatibilityDone = true;
         }
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index c29a1da..eb5fc92 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -1422,7 +1422,7 @@
          * this window is visible.
          * @hide
          */
-        @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+        @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
         public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
 
         /**
@@ -1443,6 +1443,15 @@
         public static final int PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN = 0x00200000;
 
         /**
+         * Flag to indicate that this window should be considered a screen decoration similar to the
+         * nav bar and status bar. This will cause this window to affect the window insets reported
+         * to other windows when it is visible.
+         * @hide
+         */
+        @RequiresPermission(permission.STATUS_BAR_SERVICE)
+        public static final int PRIVATE_FLAG_IS_SCREEN_DECOR = 0x00400000;
+
+        /**
          * Control flags that are private to the platform.
          * @hide
          */
@@ -1526,7 +1535,11 @@
                 @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
                         equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
-                        name = "ACQUIRES_SLEEP_TOKEN")
+                        name = "ACQUIRES_SLEEP_TOKEN"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_IS_SCREEN_DECOR,
+                        equals = PRIVATE_FLAG_IS_SCREEN_DECOR,
+                        name = "IS_SCREEN_DECOR")
         })
         @TestApi
         public int privateFlags;
diff --git a/android/view/WindowManagerGlobal.java b/android/view/WindowManagerGlobal.java
index c7e8dee..cca66d6 100644
--- a/android/view/WindowManagerGlobal.java
+++ b/android/view/WindowManagerGlobal.java
@@ -605,9 +605,10 @@
     public void setStoppedState(IBinder token, boolean stopped) {
         synchronized (mLock) {
             int count = mViews.size();
-            for (int i = 0; i < count; i++) {
+            for (int i = count - 1; i >= 0; i--) {
                 if (token == null || mParams.get(i).token == token) {
                     ViewRootImpl root = mRoots.get(i);
+                    // Client might remove the view by "stopped" event.
                     root.setWindowStopped(stopped);
                 }
             }
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
index 69cc100..cd1b190 100644
--- a/android/view/WindowManagerInternal.java
+++ b/android/view/WindowManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ClipData;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
@@ -140,6 +141,30 @@
     }
 
     /**
+     * An interface to customize drag and drop behaviors.
+     */
+    public interface IDragDropCallback {
+        /**
+         * Called when drag operation is started.
+         */
+        default boolean performDrag(IWindow window, IBinder dragToken,
+                int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+                ClipData data) {
+            return true;
+        }
+
+        /**
+         * Called when drop result is reported.
+         */
+        default void reportDropResult(IWindow window, boolean consumed) {}
+
+        /**
+         * Called when drag operation is cancelled.
+         */
+        default void cancelDragAndDrop(IBinder dragToken) {}
+    }
+
+    /**
      * Request that the window manager call
      * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
      * within a surface transaction at a later time.
@@ -225,9 +250,6 @@
      */
     public abstract boolean isKeyguardLocked();
 
-    /** @return {@code true} if the keyguard is going away. */
-    public abstract boolean isKeyguardGoingAway();
-
     /**
     * @return Whether the keyguard is showing and not occluded.
     */
@@ -354,4 +376,9 @@
      * {@param vr2dDisplayId}.
      */
     public abstract void setVr2dDisplayId(int vr2dDisplayId);
+
+    /**
+     * Sets callback to DragDropController.
+     */
+    public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
 }
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index 137e551..534335b 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -66,7 +66,6 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
@@ -722,12 +721,6 @@
     public void setInitialDisplaySize(Display display, int width, int height, int density);
 
     /**
-     * Called by window manager to set the overscan region that should be used for the
-     * given display.
-     */
-    public void setDisplayOverscan(Display display, int left, int top, int right, int bottom);
-
-    /**
      * Check permissions when adding a window.
      *
      * @param attrs The window's LayoutParams.
@@ -758,7 +751,8 @@
      * @param attrs The window layout parameters to be modified.  These values
      * are modified in-place.
      */
-    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs);
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission);
 
     /**
      * After the window manager has computed the current configuration based
@@ -1172,14 +1166,10 @@
     /**
      * Called when layout of the windows is about to start.
      *
-     * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}.
-     * @param displayWidth The current full width of the screen.
-     * @param displayHeight The current full height of the screen.
-     * @param displayRotation The current rotation being applied to the base window.
+     * @param displayFrames frames of the display we are doing layout on.
      * @param uiMode The current uiMode in configuration.
      */
-    public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
-                              int displayRotation, int uiMode);
+    default void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {}
 
     /**
      * Returns the bottom-most layer of the system decor, above which no policy decor should
@@ -1188,37 +1178,28 @@
     public int getSystemDecorLayerLw();
 
     /**
-     * Return the rectangle of the screen that is available for applications to run in.
-     * This will be called immediately after {@link #beginLayoutLw}.
-     *
-     * @param r The rectangle to be filled with the boundaries available to applications.
-     */
-    public void getContentRectLw(Rect r);
-
-    /**
-     * Called for each window attached to the window manager as layout is
-     * proceeding.  The implementation of this function must take care of
-     * setting the window's frame, either here or in finishLayout().
+     * Called for each window attached to the window manager as layout is proceeding. The
+     * implementation of this function must take care of setting the window's frame, either here or
+     * in finishLayout().
      *
      * @param win The window being positioned.
      * @param attached For sub-windows, the window it is attached to; this
      *                 window will already have had layoutWindow() called on it
      *                 so you can use its Rect.  Otherwise null.
+     * @param displayFrames The display frames.
      */
-    public void layoutWindowLw(WindowState win, WindowState attached);
+    default void layoutWindowLw(
+            WindowState win, WindowState attached, DisplayFrames displayFrames) {}
 
 
     /**
-     * Return the insets for the areas covered by system windows. These values
-     * are computed on the most recent layout, so they are not guaranteed to
-     * be correct.
+     * Return the insets for the areas covered by system windows. These values are computed on the
+     * most recent layout, so they are not guaranteed to be correct.
      *
      * @param attrs The LayoutParams of the window.
      * @param taskBounds The bounds of the task this window is on or {@code null} if no task is
      *                   associated with the window.
-     * @param displayRotation Rotation of the display.
-     * @param displayWidth The width of the display.
-     * @param displayHeight The height of the display.
+     * @param displayFrames display frames.
      * @param outContentInsets The areas covered by system windows, expressed as positive insets.
      * @param outStableInsets The areas covered by stable system windows irrespective of their
      *                        current visibility. Expressed as positive insets.
@@ -1226,16 +1207,11 @@
      * @return Whether to always consume the navigation bar.
      *         See {@link #isNavBarForcedShownLw(WindowState)}.
      */
-    public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
-            int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
-            Rect outStableInsets, Rect outOutsets);
-
-    /**
-     * Called when layout of the windows is finished.  After this function has
-     * returned, all windows given to layoutWindow() <em>must</em> have had a
-     * frame assigned.
-     */
-    public void finishLayoutLw();
+    default boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
+            DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+            Rect outOutsets) {
+        return false;
+    }
 
     /** Layout state may have changed (so another layout will be performed) */
     static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
@@ -1652,11 +1628,6 @@
     public void showGlobalActions();
 
     /**
-     * @return The current height of the input method window.
-     */
-    public int getInputMethodWindowVisibleHeightLw();
-
-    /**
      * Called when the current user changes. Guaranteed to be called before the broadcast
      * of the new user id is made to all listeners.
      *
diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java
index 19213ca..c3d6c69 100644
--- a/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/android/view/accessibility/AccessibilityInteractionClient.java
@@ -187,8 +187,11 @@
                     Log.i(LOG_TAG, "Window cache miss");
                 }
                 final long identityToken = Binder.clearCallingIdentity();
-                window = connection.getWindow(accessibilityWindowId);
-                Binder.restoreCallingIdentity(identityToken);
+                try {
+                    window = connection.getWindow(accessibilityWindowId);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
                 if (window != null) {
                     sAccessibilityCache.addWindow(window);
                     return window;
@@ -225,8 +228,11 @@
                     Log.i(LOG_TAG, "Windows cache miss");
                 }
                 final long identityToken = Binder.clearCallingIdentity();
-                windows = connection.getWindows();
-                Binder.restoreCallingIdentity(identityToken);
+                try {
+                    windows = connection.getWindows();
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
                 if (windows != null) {
                     sAccessibilityCache.setWindows(windows);
                     return windows;
@@ -283,10 +289,14 @@
                 }
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final long identityToken = Binder.clearCallingIdentity();
-                final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
-                        accessibilityWindowId, accessibilityNodeId, interactionId, this,
-                        prefetchFlags, Thread.currentThread().getId(), arguments);
-                Binder.restoreCallingIdentity(identityToken);
+                final boolean success;
+                try {
+                    success = connection.findAccessibilityNodeInfoByAccessibilityId(
+                            accessibilityWindowId, accessibilityNodeId, interactionId, this,
+                            prefetchFlags, Thread.currentThread().getId(), arguments);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
                 if (success) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
@@ -333,10 +343,15 @@
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final long identityToken = Binder.clearCallingIdentity();
-                final boolean success = connection.findAccessibilityNodeInfosByViewId(
-                        accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
-                        Thread.currentThread().getId());
-                Binder.restoreCallingIdentity(identityToken);
+                final boolean success;
+                try {
+                    success = connection.findAccessibilityNodeInfosByViewId(
+                            accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
                 if (success) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
@@ -381,10 +396,15 @@
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final long identityToken = Binder.clearCallingIdentity();
-                final boolean success = connection.findAccessibilityNodeInfosByText(
-                        accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
-                        Thread.currentThread().getId());
-                Binder.restoreCallingIdentity(identityToken);
+                final boolean success;
+                try {
+                    success = connection.findAccessibilityNodeInfosByText(
+                            accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
                 if (success) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
@@ -428,10 +448,15 @@
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final long identityToken = Binder.clearCallingIdentity();
-                final boolean success = connection.findFocus(accessibilityWindowId,
-                        accessibilityNodeId, focusType, interactionId, this,
-                        Thread.currentThread().getId());
-                Binder.restoreCallingIdentity(identityToken);
+                final boolean success;
+                try {
+                    success = connection.findFocus(accessibilityWindowId,
+                            accessibilityNodeId, focusType, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
                 if (success) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
@@ -472,10 +497,15 @@
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final long identityToken = Binder.clearCallingIdentity();
-                final boolean success = connection.focusSearch(accessibilityWindowId,
-                        accessibilityNodeId, direction, interactionId, this,
-                        Thread.currentThread().getId());
-                Binder.restoreCallingIdentity(identityToken);
+                final boolean success;
+                try {
+                    success = connection.focusSearch(accessibilityWindowId,
+                            accessibilityNodeId, direction, interactionId, this,
+                            Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
                 if (success) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
@@ -515,10 +545,15 @@
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 final long identityToken = Binder.clearCallingIdentity();
-                final boolean success = connection.performAccessibilityAction(
-                        accessibilityWindowId, accessibilityNodeId, action, arguments,
-                        interactionId, this, Thread.currentThread().getId());
-                Binder.restoreCallingIdentity(identityToken);
+                final boolean success;
+                try {
+                    success = connection.performAccessibilityAction(
+                            accessibilityWindowId, accessibilityNodeId, action, arguments,
+                            interactionId, this, Thread.currentThread().getId());
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+
                 if (success) {
                     return getPerformAccessibilityActionResultAndClear(interactionId);
                 }
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc57..35f6acb 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -436,8 +436,11 @@
             // 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);
+            try {
+                service.sendAccessibilityEvent(event, userId);
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
             if (DEBUG) {
                 Log.i(LOG_TAG, event + " sent");
             }
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index e79d201..9241ec0 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -54,6 +54,9 @@
 import java.util.List;
 import java.util.Objects;
 
+// TODO: use java.lang.ref.Cleaner once Android supports Java 9
+import sun.misc.Cleaner;
+
 /**
  * The {@link AutofillManager} provides ways for apps and custom views to integrate with the
  * Autofill Framework lifecycle.
@@ -303,6 +306,9 @@
     private IAutoFillManagerClient mServiceClient;
 
     @GuardedBy("mLock")
+    private Cleaner mServiceClientCleaner;
+
+    @GuardedBy("mLock")
     private AutofillCallback mCallback;
 
     private final Context mContext;
@@ -1172,10 +1178,19 @@
         if (mServiceClient == null) {
             mServiceClient = new AutofillManagerClient(this);
             try {
-                final int flags = mService.addClient(mServiceClient, mContext.getUserId());
+                final int userId = mContext.getUserId();
+                final int flags = mService.addClient(mServiceClient, userId);
                 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
                 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
                 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
+                final IAutoFillManager service = mService;
+                final IAutoFillManagerClient serviceClient = mServiceClient;
+                mServiceClientCleaner = Cleaner.create(this, () -> {
+                    try {
+                        service.removeClient(serviceClient, userId);
+                    } catch (RemoteException e) {
+                    }
+                });
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1272,18 +1287,36 @@
         }
     }
 
-    private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+    /** @hide */
+    public static final int SET_STATE_FLAG_ENABLED = 0x01;
+    /** @hide */
+    public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
+    /** @hide */
+    public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
+    /** @hide */
+    public static final int SET_STATE_FLAG_DEBUG = 0x08;
+    /** @hide */
+    public static final int SET_STATE_FLAG_VERBOSE = 0x10;
+
+    private void setState(int flags) {
+        if (sVerbose) Log.v(TAG, "setState(" + flags + ")");
         synchronized (mLock) {
-            mEnabled = enabled;
-            if (!mEnabled || resetSession) {
+            mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
+            if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
                 // Reset the session state
                 resetSessionLocked();
             }
-            if (resetClient) {
+            if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) {
                 // Reset connection to system
                 mServiceClient = null;
+                if (mServiceClientCleaner != null) {
+                    mServiceClientCleaner.clean();
+                    mServiceClientCleaner = null;
+                }
             }
         }
+        sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
+        sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0;
     }
 
     /**
@@ -1609,6 +1642,7 @@
         pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
         pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
         pw.print(pfx); pw.print("context: "); pw.println(mContext);
+        pw.print(pfx); pw.print("client: "); pw.println(getClientLocked());
         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);
@@ -1625,6 +1659,8 @@
         pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
         pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
         pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
+        pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
+        pw.print(" verbose: "); pw.println(sVerbose);
     }
 
     private String getStateAsStringLocked() {
@@ -1880,7 +1916,7 @@
     public abstract static class AutofillCallback {
 
         /** @hide */
-        @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
+        @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN, EVENT_INPUT_UNAVAILABLE})
         @Retention(RetentionPolicy.SOURCE)
         public @interface AutofillEventType {}
 
@@ -1940,10 +1976,10 @@
         }
 
         @Override
-        public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+        public void setState(int flags) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() -> afm.setState(enabled, resetSession, resetClient));
+                afm.post(() -> afm.setState(flags));
             }
         }
 
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 26d2141..2779aa2 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.os.LocaleList;
 import android.view.View.OnClickListener;
 import android.view.textclassifier.TextClassifier.EntityType;
 
@@ -438,4 +439,31 @@
                     mLogType, mVersionInfo);
         }
     }
+
+    /**
+     * TextClassification optional input parameters.
+     */
+    public static final class Options {
+
+        private LocaleList mDefaultLocales;
+
+        /**
+         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+         *      the provided text. If no locale preferences exist, set this to null or an empty
+         *      locale list.
+         */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
+        }
+
+        /**
+         * @return ordered list of locale preferences that can be used to disambiguate
+         *      the provided text.
+         */
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+    }
 }
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index 46dbd0e..aeb8489 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -56,23 +56,7 @@
      * No-op TextClassifier.
      * This may be used to turn off TextClassifier features.
      */
-    TextClassifier NO_OP = new TextClassifier() {
-
-        @Override
-        public TextSelection suggestSelection(
-                CharSequence text,
-                int selectionStartIndex,
-                int selectionEndIndex,
-                LocaleList defaultLocales) {
-            return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
-        }
-
-        @Override
-        public TextClassification classifyText(
-                CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales) {
-            return TextClassification.EMPTY;
-        }
-    };
+    TextClassifier NO_OP = new TextClassifier() {};
 
     /**
      * Returns suggested text selection start and end indices, recognized entity types, and their
@@ -82,21 +66,34 @@
      *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
      * @param selectionStartIndex start index of the selected part of text
      * @param selectionEndIndex end index of the selected part of text
-     * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
-     *      the provided text. If no locale preferences exist, set this to null or an empty locale
-     *      list in which case the classifier will decide whether to use no locale information, use
-     *      a default locale, or use the system default.
+     * @param options optional input parameters
      *
      * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
      *      selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
      */
     @WorkerThread
     @NonNull
-    TextSelection suggestSelection(
+    default TextSelection suggestSelection(
             @NonNull CharSequence text,
             @IntRange(from = 0) int selectionStartIndex,
             @IntRange(from = 0) int selectionEndIndex,
-            @Nullable LocaleList defaultLocales);
+            @Nullable TextSelection.Options options) {
+        return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+    }
+
+    /**
+     * @see #suggestSelection(CharSequence, int, int, TextSelection.Options)
+     */
+    // TODO: Consider deprecating (b/68846316)
+    @WorkerThread
+    @NonNull
+    default TextSelection suggestSelection(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int selectionStartIndex,
+            @IntRange(from = 0) int selectionEndIndex,
+            @Nullable LocaleList defaultLocales) {
+        return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+    }
 
     /**
      * Classifies the specified text and returns a {@link TextClassification} object that can be
@@ -106,41 +103,48 @@
      *      by the sub sequence starting at startIndex and ending at endIndex)
      * @param startIndex start index of the text to classify
      * @param endIndex end index of the text to classify
-     * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
-     *      the provided text. If no locale preferences exist, set this to null or an empty locale
-     *      list in which case the classifier will decide whether to use no locale information, use
-     *      a default locale, or use the system default.
+     * @param options optional input parameters
      *
      * @throws IllegalArgumentException if text is null; startIndex is negative;
      *      endIndex is greater than text.length() or not greater than startIndex
      */
     @WorkerThread
     @NonNull
-    TextClassification classifyText(
+    default TextClassification classifyText(
             @NonNull CharSequence text,
             @IntRange(from = 0) int startIndex,
             @IntRange(from = 0) int endIndex,
-            @Nullable LocaleList defaultLocales);
+            @Nullable TextClassification.Options options) {
+        return TextClassification.EMPTY;
+    }
 
     /**
-     * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+     * @see #classifyText(CharSequence, int, int, TextClassification.Options)
+     */
+    // TODO: Consider deprecating (b/68846316)
+    @WorkerThread
+    @NonNull
+    default TextClassification classifyText(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int startIndex,
+            @IntRange(from = 0) int endIndex,
+            @Nullable LocaleList defaultLocales) {
+        return TextClassification.EMPTY;
+    }
+
+    /**
+     * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
      * information.
      *
      * @param text the text to generate annotations for
-     * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
-     *      specified. Subclasses of this interface may specify additional linkMasks
-     * @param defaultLocales  ordered list of locale preferences that can be used to disambiguate
-     *      the provided text. If no locale preferences exist, set this to null or an empty locale
-     *      list in which case the classifier will decide whether to use no locale information, use
-     *      a default locale, or use the system default.
+     * @param options configuration for link generation. If null, defaults will be used.
      *
      * @throws IllegalArgumentException if text is null
-     * @hide
      */
     @WorkerThread
-    default LinksInfo getLinks(
-            @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
-        return LinksInfo.NO_OP;
+    default TextLinks generateLinks(
+            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+        return new TextLinks.Builder(text.toString()).build();
     }
 
     /**
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 1c07be4..2ad6e02 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -30,13 +30,8 @@
 import android.provider.Browser;
 import android.provider.ContactsContract;
 import android.provider.Settings;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.WordIterator;
-import android.text.style.ClickableSpan;
 import android.text.util.Linkify;
 import android.util.Patterns;
-import android.view.View;
 import android.widget.TextViewMetrics;
 
 import com.android.internal.annotations.GuardedBy;
@@ -46,13 +41,8 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.text.BreakIterator;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -100,16 +90,24 @@
     @Override
     public TextSelection suggestSelection(
             @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
-            @Nullable LocaleList defaultLocales) {
+            @Nullable TextSelection.Options options) {
         validateInput(text, selectionStartIndex, selectionEndIndex);
         try {
             if (text.length() > 0) {
-                final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+                final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+                final SmartSelection smartSelection = getSmartSelection(locales);
                 final String string = text.toString();
-                final int[] startEnd = smartSelection.suggest(
-                        string, selectionStartIndex, selectionEndIndex);
-                final int start = startEnd[0];
-                final int end = startEnd[1];
+                final int start;
+                final int end;
+                if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) {
+                    start = selectionStartIndex;
+                    end = selectionEndIndex;
+                } else {
+                    final int[] startEnd = smartSelection.suggest(
+                            string, selectionStartIndex, selectionEndIndex);
+                    start = startEnd[0];
+                    end = startEnd[1];
+                }
                 if (start <= end
                         && start >= 0 && end <= string.length()
                         && start <= selectionStartIndex && end >= selectionEndIndex) {
@@ -139,18 +137,27 @@
         }
         // Getting here means something went wrong, return a NO_OP result.
         return TextClassifier.NO_OP.suggestSelection(
-                text, selectionStartIndex, selectionEndIndex, defaultLocales);
+                text, selectionStartIndex, selectionEndIndex, options);
+    }
+
+    @Override
+    public TextSelection suggestSelection(
+            @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
+            @Nullable LocaleList defaultLocales) {
+        return suggestSelection(text, selectionStartIndex, selectionEndIndex,
+                new TextSelection.Options().setDefaultLocales(defaultLocales));
     }
 
     @Override
     public TextClassification classifyText(
             @NonNull CharSequence text, int startIndex, int endIndex,
-            @Nullable LocaleList defaultLocales) {
+            @Nullable TextClassification.Options options) {
         validateInput(text, startIndex, endIndex);
         try {
             if (text.length() > 0) {
                 final String string = text.toString();
-                SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales)
+                final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+                final SmartSelection.ClassificationResult[] results = getSmartSelection(locales)
                         .classifyText(string, startIndex, endIndex,
                                 getHintFlags(string, startIndex, endIndex));
                 if (results.length > 0) {
@@ -165,23 +172,41 @@
             Log.e(LOG_TAG, "Error getting text classification info.", t);
         }
         // Getting here means something went wrong, return a NO_OP result.
-        return TextClassifier.NO_OP.classifyText(
-                text, startIndex, endIndex, defaultLocales);
+        return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options);
     }
 
     @Override
-    public LinksInfo getLinks(
-            @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
-        Preconditions.checkArgument(text != null);
+    public TextClassification classifyText(
+            @NonNull CharSequence text, int startIndex, int endIndex,
+            @Nullable LocaleList defaultLocales) {
+        return classifyText(text, startIndex, endIndex,
+                new TextClassification.Options().setDefaultLocales(defaultLocales));
+    }
+
+    @Override
+    public TextLinks generateLinks(
+            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+        Preconditions.checkNotNull(text);
+        final String textString = text.toString();
+        final TextLinks.Builder builder = new TextLinks.Builder(textString);
         try {
-            return LinksInfoFactory.create(
-                    mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
+            LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+            final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+            final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
+            for (SmartSelection.AnnotatedSpan span : annotations) {
+                final Map<String, Float> entityScores = new HashMap<>();
+                final SmartSelection.ClassificationResult[] results = span.getClassification();
+                for (int i = 0; i < results.length; i++) {
+                    entityScores.put(results[i].mCollection, results[i].mScore);
+                }
+                builder.addLink(new TextLinks.TextLink(
+                        textString, span.getStartIndex(), span.getEndIndex(), entityScores));
+            }
         } catch (Throwable t) {
             // Avoid throwing from this method. Log the error.
             Log.e(LOG_TAG, "Error getting links info.", t);
         }
-        // Getting here means something went wrong, return a NO_OP result.
-        return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
+        return builder.build();
     }
 
     @Override
@@ -210,7 +235,9 @@
             if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
                 destroySmartSelectionIfExistsLocked();
                 final ParcelFileDescriptor fd = getFdLocked(locale);
-                mSmartSelection = new SmartSelection(fd.getFd());
+                final int modelFd = fd.getFd();
+                mVersion = SmartSelection.getVersion(modelFd);
+                mSmartSelection = new SmartSelection(modelFd);
                 closeAndLogError(fd);
                 mLocale = locale;
             }
@@ -231,18 +258,26 @@
     @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
     private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
         ParcelFileDescriptor updateFd;
+        int updateVersion = -1;
         try {
             updateFd = ParcelFileDescriptor.open(
                     new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
+            if (updateFd != null) {
+                updateVersion = SmartSelection.getVersion(updateFd.getFd());
+            }
         } catch (FileNotFoundException e) {
             updateFd = null;
         }
         ParcelFileDescriptor factoryFd;
+        int factoryVersion = -1;
         try {
             final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
             if (factoryModelFilePath != null) {
                 factoryFd = ParcelFileDescriptor.open(
                         new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
+                if (factoryFd != null) {
+                    factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
+                }
             } else {
                 factoryFd = null;
             }
@@ -278,15 +313,11 @@
             return factoryFd;
         }
 
-        final int updateVersion = SmartSelection.getVersion(updateFdInt);
-        final int factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
         if (updateVersion > factoryVersion) {
             closeAndLogError(factoryFd);
-            mVersion = updateVersion;
             return updateFd;
         } else {
             closeAndLogError(updateFd);
-            mVersion = factoryVersion;
             return factoryFd;
         }
     }
@@ -466,180 +497,6 @@
     }
 
     /**
-     * Detects and creates links for specified text.
-     */
-    private static final class LinksInfoFactory {
-
-        private LinksInfoFactory() {}
-
-        public static LinksInfo create(
-                Context context, SmartSelection smartSelection, String text, int linkMask) {
-            final WordIterator wordIterator = new WordIterator();
-            wordIterator.setCharSequence(text, 0, text.length());
-            final List<SpanSpec> spans = new ArrayList<>();
-            int start = 0;
-            int end;
-            while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
-                final String token = text.substring(start, end);
-                if (TextUtils.isEmpty(token)) {
-                    continue;
-                }
-
-                final int[] selection = smartSelection.suggest(text, start, end);
-                final int selectionStart = selection[0];
-                final int selectionEnd = selection[1];
-                if (selectionStart >= 0 && selectionEnd <= text.length()
-                        && selectionStart <= selectionEnd) {
-                    final SmartSelection.ClassificationResult[] results =
-                            smartSelection.classifyText(
-                                    text, selectionStart, selectionEnd,
-                                    getHintFlags(text, selectionStart, selectionEnd));
-                    if (results.length > 0) {
-                        final String type = getHighestScoringType(results);
-                        if (matches(type, linkMask)) {
-                            // For links without disambiguation, we simply use the default intent.
-                            final List<Intent> intents = IntentFactory.create(
-                                    context, type, text.substring(selectionStart, selectionEnd));
-                            if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
-                                final ClickableSpan span = createSpan(context, intents.get(0));
-                                spans.add(new SpanSpec(selectionStart, selectionEnd, span));
-                            }
-                        }
-                    }
-                }
-                start = end;
-            }
-            return new LinksInfoImpl(text, avoidOverlaps(spans, text));
-        }
-
-        /**
-         * Returns true if the classification type matches the specified linkMask.
-         */
-        private static boolean matches(String type, int linkMask) {
-            type = type.trim().toLowerCase(Locale.ENGLISH);
-            if ((linkMask & Linkify.PHONE_NUMBERS) != 0
-                    && TextClassifier.TYPE_PHONE.equals(type)) {
-                return true;
-            }
-            if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
-                    && TextClassifier.TYPE_EMAIL.equals(type)) {
-                return true;
-            }
-            if ((linkMask & Linkify.MAP_ADDRESSES) != 0
-                    && TextClassifier.TYPE_ADDRESS.equals(type)) {
-                return true;
-            }
-            if ((linkMask & Linkify.WEB_URLS) != 0
-                    && TextClassifier.TYPE_URL.equals(type)) {
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Trim the number of spans so that no two spans overlap.
-         *
-         * This algorithm first ensures that there is only one span per start index, then it
-         * makes sure that no two spans overlap.
-         */
-        private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
-            Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
-            // Group spans by start index. Take the longest span.
-            final Map<Integer, SpanSpec> reps = new LinkedHashMap<>();  // order matters.
-            final int size = spans.size();
-            for (int i = 0; i < size; i++) {
-                final SpanSpec span = spans.get(i);
-                final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
-                if (rep == null || rep.mEnd < span.mEnd) {
-                    reps.put(span.mStart, span);
-                }
-            }
-            // Avoid span intersections. Take the longer span.
-            final LinkedList<SpanSpec> result = new LinkedList<>();
-            for (SpanSpec rep : reps.values()) {
-                if (result.isEmpty()) {
-                    result.add(rep);
-                    continue;
-                }
-
-                final SpanSpec last = result.getLast();
-                if (rep.mStart < last.mEnd) {
-                    // Spans intersect. Use the one with characters.
-                    if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
-                        result.set(result.size() - 1, rep);
-                    }
-                } else {
-                    result.add(rep);
-                }
-            }
-            return result;
-        }
-
-        private static ClickableSpan createSpan(final Context context, final Intent intent) {
-            return new ClickableSpan() {
-                // TODO: Style this span.
-                @Override
-                public void onClick(View widget) {
-                    context.startActivity(intent);
-                }
-            };
-        }
-
-        private static boolean hasActivityHandler(Context context, Intent intent) {
-            if (intent == null) {
-                return false;
-            }
-            final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
-            return resolveInfo != null && resolveInfo.activityInfo != null;
-        }
-
-        /**
-         * Implementation of LinksInfo that adds ClickableSpans to the specified text.
-         */
-        private static final class LinksInfoImpl implements LinksInfo {
-
-            private final CharSequence mOriginalText;
-            private final List<SpanSpec> mSpans;
-
-            LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
-                mOriginalText = originalText;
-                mSpans = spans;
-            }
-
-            @Override
-            public boolean apply(@NonNull CharSequence text) {
-                Preconditions.checkArgument(text != null);
-                if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
-                    Spannable spannable = (Spannable) text;
-                    final int size = mSpans.size();
-                    for (int i = 0; i < size; i++) {
-                        final SpanSpec span = mSpans.get(i);
-                        spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
-                    }
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        /**
-         * Span plus its start and end index.
-         */
-        private static final class SpanSpec {
-
-            private final int mStart;
-            private final int mEnd;
-            private final ClickableSpan mSpan;
-
-            SpanSpec(int start, int end, ClickableSpan span) {
-                mStart = start;
-                mEnd = end;
-                mSpan = span;
-            }
-        }
-    }
-
-    /**
      * Creates intents based on the classification type.
      */
     private static final class IntentFactory {
@@ -656,8 +513,8 @@
                     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));
+                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                            .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
                     break;
                 case TextClassifier.TYPE_PHONE:
                     intents.add(new Intent(Intent.ACTION_DIAL)
diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java
new file mode 100644
index 0000000..f3cc827
--- /dev/null
+++ b/android/view/textclassifier/TextLinks.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
+import android.text.SpannableString;
+import android.text.style.ClickableSpan;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A collection of links, representing subsequences of text and the entity types (phone number,
+ * address, url, etc) they may be.
+ */
+public final class TextLinks {
+    private final String mFullText;
+    private final List<TextLink> mLinks;
+
+    private TextLinks(String fullText, Collection<TextLink> links) {
+        mFullText = fullText;
+        mLinks = Collections.unmodifiableList(new ArrayList<>(links));
+    }
+
+    /**
+     * Returns an unmodifiable Collection of the links.
+     */
+    public Collection<TextLink> getLinks() {
+        return mLinks;
+    }
+
+    /**
+     * Annotates the given text with the generated links. It will fail if the provided text doesn't
+     * match the original text used to crete the TextLinks.
+     *
+     * @param text the text to apply the links to. Must match the original text.
+     * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null.
+     *
+     * @return Success or failure.
+     */
+    public boolean apply(
+            @NonNull SpannableString text,
+            @Nullable Function<TextLink, ClickableSpan> spanFactory) {
+        Preconditions.checkNotNull(text);
+        if (!mFullText.equals(text.toString())) {
+            return false;
+        }
+
+        if (spanFactory == null) {
+            spanFactory = DEFAULT_SPAN_FACTORY;
+        }
+        for (TextLink link : mLinks) {
+            final ClickableSpan span = spanFactory.apply(link);
+            if (span != null) {
+                text.setSpan(span, link.getStart(), link.getEnd(), 0);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * A link, identifying a substring of text and possible entity types for it.
+     */
+    public static final class TextLink {
+        private final EntityConfidence<String> mEntityScores;
+        private final String mOriginalText;
+        private final int mStart;
+        private final int mEnd;
+
+        /**
+         * Create a new TextLink.
+         *
+         * @throws IllegalArgumentException if entityScores is null or empty.
+         */
+        public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) {
+            Preconditions.checkNotNull(originalText);
+            Preconditions.checkNotNull(entityScores);
+            Preconditions.checkArgument(!entityScores.isEmpty());
+            Preconditions.checkArgument(start <= end);
+            mOriginalText = originalText;
+            mStart = start;
+            mEnd = end;
+            mEntityScores = new EntityConfidence<>();
+
+            for (Map.Entry<String, Float> entry : entityScores.entrySet()) {
+                mEntityScores.setEntityType(entry.getKey(), entry.getValue());
+            }
+        }
+
+        /**
+         * Returns the start index of this link in the original text.
+         *
+         * @return the start index.
+         */
+        public int getStart() {
+            return mStart;
+        }
+
+        /**
+         * Returns the end index of this link in the original text.
+         *
+         * @return the end index.
+         */
+        public int getEnd() {
+            return mEnd;
+        }
+
+        /**
+         * Returns the number of entity types that have confidence scores.
+         *
+         * @return the entity count.
+         */
+        public int getEntityCount() {
+            return mEntityScores.getEntities().size();
+        }
+
+        /**
+         * Returns the entity type at a given index. Entity types are sorted by confidence.
+         *
+         * @return the entity type at the provided index.
+         */
+        @NonNull public @TextClassifier.EntityType String getEntity(int index) {
+            return mEntityScores.getEntities().get(index);
+        }
+
+        /**
+         * Returns the confidence score for a particular entity type.
+         *
+         * @param entityType the entity type.
+         */
+        public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
+                @TextClassifier.EntityType String entityType) {
+            return mEntityScores.getConfidenceScore(entityType);
+        }
+    }
+
+    /**
+     * Optional input parameters for generating TextLinks.
+     */
+    public static final class Options {
+        private final LocaleList mLocaleList;
+
+        private Options(LocaleList localeList) {
+            this.mLocaleList = localeList;
+        }
+
+        /**
+         * Builder to construct Options.
+         */
+        public static final class Builder {
+            private LocaleList mLocaleList;
+
+            /**
+             * Sets the LocaleList to use.
+             *
+             * @return this Builder.
+             */
+            public Builder setLocaleList(@Nullable LocaleList localeList) {
+                this.mLocaleList = localeList;
+                return this;
+            }
+
+            /**
+             * Builds the Options object.
+             */
+            public Options build() {
+                return new Options(mLocaleList);
+            }
+        }
+        public @Nullable LocaleList getDefaultLocales() {
+            return mLocaleList;
+        }
+    };
+
+    /**
+     * A function to create spans from TextLinks.
+     *
+     * Applies only to TextViews.
+     * We can hide this until we are convinced we want it to be part of the public API.
+     *
+     * @hide
+     */
+    public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
+            new Function<TextLink, ClickableSpan>() {
+        @Override
+        public ClickableSpan apply(TextLink textLink) {
+            // TODO: Implement.
+            throw new UnsupportedOperationException("Not yet implemented");
+        }
+    };
+
+    /**
+     * A builder to construct a TextLinks instance.
+     */
+    public static final class Builder {
+        private final String mFullText;
+        private final Collection<TextLink> mLinks;
+
+        /**
+         * Create a new TextLinks.Builder.
+         *
+         * @param fullText The full text that links will be added to.
+         */
+        public Builder(@NonNull String fullText) {
+            mFullText = Preconditions.checkNotNull(fullText);
+            mLinks = new ArrayList<>();
+        }
+
+        /**
+         * Adds a TextLink.
+         *
+         * @return this instance.
+         */
+        public Builder addLink(TextLink link) {
+            Preconditions.checkNotNull(link);
+            mLinks.add(link);
+            return this;
+        }
+
+        /**
+         * Constructs a TextLinks instance.
+         *
+         * @return the constructed TextLinks.
+         */
+        public TextLinks build() {
+            return new TextLinks(mFullText, mLinks);
+        }
+    }
+}
diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java
index 11ebe83..0a67954 100644
--- a/android/view/textclassifier/TextSelection.java
+++ b/android/view/textclassifier/TextSelection.java
@@ -19,6 +19,8 @@
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
 import android.view.textclassifier.TextClassifier.EntityType;
 
 import com.android.internal.util.Preconditions;
@@ -181,4 +183,55 @@
                     mStartIndex, mEndIndex, mEntityConfidence, mLogSource, mVersionInfo);
         }
     }
+
+    /**
+     * TextSelection optional input parameters.
+     */
+    public static final class Options {
+
+        private LocaleList mDefaultLocales;
+        private boolean mDarkLaunchAllowed;
+
+        /**
+         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+         *      the provided text. If no locale preferences exist, set this to null or an empty
+         *      locale list.
+         */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
+        }
+
+        /**
+         * @return ordered list of locale preferences that can be used to disambiguate
+         *      the provided text.
+         */
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
+        }
+
+        /**
+         * @param allowed whether or not the TextClassifier should return selection suggestions
+         *      when "dark launched". When a TextClassifier is dark launched, it can suggest
+         *      selection changes that should not be used to actually change the user's selection.
+         *      Instead, the suggested selection is logged, compared with the user's selection
+         *      interaction, and used to generate quality metrics for the TextClassifier.
+         *
+         * @hide
+         */
+        public void setDarkLaunchAllowed(boolean allowed) {
+            mDarkLaunchAllowed = allowed;
+        }
+
+        /**
+         * Returns true if the TextClassifier should return selection suggestions when
+         * "dark launched". Otherwise, returns false.
+         *
+         * @hide
+         */
+        public boolean isDarkLaunchAllowed() {
+            return mDarkLaunchAllowed;
+        }
+    }
 }
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 83af19b..2833564 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -48,31 +48,45 @@
     private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
     private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
     private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
-    private static final int VERSION_TAG = MetricsEvent.FIELD_SELECTION_VERSION_TAG;
-    private static final int SMART_INDICES = MetricsEvent.FIELD_SELECTION_SMART_RANGE;
-    private static final int EVENT_INDICES = MetricsEvent.FIELD_SELECTION_RANGE;
+    private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
+    private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
+    private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
+    private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
+    private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
+    private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
+    private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
+    private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
     private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
 
     private static final String ZERO = "0";
     private static final String TEXTVIEW = "textview";
     private static final String EDITTEXT = "edittext";
+    private static final String UNSELECTABLE_TEXTVIEW = "nosel-textview";
     private static final String WEBVIEW = "webview";
     private static final String EDIT_WEBVIEW = "edit-webview";
+    private static final String CUSTOM_TEXTVIEW = "customview";
+    private static final String CUSTOM_EDITTEXT = "customedit";
+    private static final String CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
     private static final String UNKNOWN = "unknown";
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW,
             WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW})
     public @interface WidgetType {
-    int UNSPECIFIED = 0;
-    int TEXTVIEW = 1;
-    int WEBVIEW = 2;
-    int EDITTEXT = 3;
-    int EDIT_WEBVIEW = 4;
+        int UNSPECIFIED = 0;
+        int TEXTVIEW = 1;
+        int WEBVIEW = 2;
+        int EDITTEXT = 3;
+        int EDIT_WEBVIEW = 4;
+        int UNSELECTABLE_TEXTVIEW = 5;
+        int CUSTOM_TEXTVIEW = 6;
+        int CUSTOM_EDITTEXT = 7;
+        int CUSTOM_UNSELECTABLE_TEXTVIEW = 8;
     }
 
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
     private final int mWidgetType;
+    @Nullable private final String mWidgetVersion;
     private final Context mContext;
 
     @Nullable private String mSessionId;
@@ -83,10 +97,18 @@
     private long mSessionStartTime;
     private long mLastEventTime;
     private boolean mSmartSelectionTriggered;
-    private String mVersionTag;
+    private String mModelName;
 
     public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
         mWidgetType = widgetType;
+        mWidgetVersion = null;
+        mContext = Preconditions.checkNotNull(context);
+    }
+
+    public SmartSelectionEventTracker(
+            @NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) {
+        mWidgetType = widgetType;
+        mWidgetVersion = widgetVersion;
         mContext = Preconditions.checkNotNull(context);
     }
 
@@ -115,7 +137,7 @@
             case SelectionEvent.EventType.SMART_SELECTION_SINGLE:  // fall through
             case SelectionEvent.EventType.SMART_SELECTION_MULTI:
                 mSmartSelectionTriggered = true;
-                mVersionTag = getVersionTag(event);
+                mModelName = getModelName(event);
                 mSmartIndices[0] = event.mStart;
                 mSmartIndices[1] = event.mEnd;
                 break;
@@ -137,14 +159,19 @@
         final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime;
         final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
                 .setType(getLogType(event))
-                .setSubtype(getLogSubType(event))
+                .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL)
                 .setPackageName(mContext.getPackageName())
                 .addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
                 .addTaggedData(PREV_EVENT_DELTA, prevEventDelta)
                 .addTaggedData(INDEX, mIndex)
-                .addTaggedData(VERSION_TAG, mVersionTag)
-                .addTaggedData(SMART_INDICES, getSmartDelta())
-                .addTaggedData(EVENT_INDICES, getEventDelta(event))
+                .addTaggedData(WIDGET_TYPE, getWidgetTypeName())
+                .addTaggedData(WIDGET_VERSION, mWidgetVersion)
+                .addTaggedData(MODEL_NAME, mModelName)
+                .addTaggedData(ENTITY_TYPE, event.mEntityType)
+                .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0]))
+                .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1]))
+                .addTaggedData(EVENT_START, getRangeDelta(event.mStart))
+                .addTaggedData(EVENT_END, getRangeDelta(event.mEnd))
                 .addTaggedData(SESSION_ID, mSessionId);
         mMetricsLogger.write(log);
         debugLog(log);
@@ -169,7 +196,7 @@
         mSessionStartTime = 0;
         mLastEventTime = 0;
         mSmartSelectionTriggered = false;
-        mVersionTag = getVersionTag(null);
+        mModelName = getModelName(null);
         mSessionId = null;
     }
 
@@ -251,113 +278,75 @@
         }
     }
 
-    private static int getLogSubType(SelectionEvent event) {
-        switch (event.mEntityType) {
-            case TextClassifier.TYPE_OTHER:
-                return MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER;
-            case TextClassifier.TYPE_EMAIL:
-                return MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL;
-            case TextClassifier.TYPE_PHONE:
-                return MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE;
-            case TextClassifier.TYPE_ADDRESS:
-                return MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS;
-            case TextClassifier.TYPE_URL:
-                return MetricsEvent.TEXT_CLASSIFIER_TYPE_URL;
-            default:
-                return MetricsEvent.TEXT_CLASSIFIER_TYPE_UNKNOWN;
-        }
+    private int getRangeDelta(int offset) {
+        return offset - mOrigStart;
     }
 
-    private static String getLogSubTypeString(int logSubType) {
-        switch (logSubType) {
-            case MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER:
-                return TextClassifier.TYPE_OTHER;
-            case MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL:
-                return TextClassifier.TYPE_EMAIL;
-            case MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE:
-                return TextClassifier.TYPE_PHONE;
-            case MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS:
-                return TextClassifier.TYPE_ADDRESS;
-            case MetricsEvent.TEXT_CLASSIFIER_TYPE_URL:
-                return TextClassifier.TYPE_URL;
-            default:
-                return TextClassifier.TYPE_UNKNOWN;
-        }
+    private int getSmartRangeDelta(int offset) {
+        return mSmartSelectionTriggered ? getRangeDelta(offset) : 0;
     }
 
-    private int getSmartDelta() {
-        if (mSmartSelectionTriggered) {
-            return (clamp(mSmartIndices[0] - mOrigStart) << 16)
-                    | (clamp(mSmartIndices[1] - mOrigStart) & 0xffff);
-        }
-        // If the smart selection model was not run, return invalid selection indices [0,0]. This
-        // allows us to tell from the terminal event alone whether the model was run.
-        return 0;
-    }
-
-    private int getEventDelta(SelectionEvent event) {
-        return (clamp(event.mStart - mOrigStart) << 16)
-                | (clamp(event.mEnd - mOrigStart) & 0xffff);
-    }
-
-    private String getVersionTag(@Nullable SelectionEvent event) {
-        final String widgetType;
+    private String getWidgetTypeName() {
         switch (mWidgetType) {
             case WidgetType.TEXTVIEW:
-                widgetType = TEXTVIEW;
-                break;
+                return TEXTVIEW;
             case WidgetType.WEBVIEW:
-                widgetType = WEBVIEW;
-                break;
+                return WEBVIEW;
             case WidgetType.EDITTEXT:
-                widgetType = EDITTEXT;
-                break;
+                return EDITTEXT;
             case WidgetType.EDIT_WEBVIEW:
-                widgetType = EDIT_WEBVIEW;
-                break;
+                return EDIT_WEBVIEW;
+            case WidgetType.UNSELECTABLE_TEXTVIEW:
+                return UNSELECTABLE_TEXTVIEW;
+            case WidgetType.CUSTOM_TEXTVIEW:
+                return CUSTOM_TEXTVIEW;
+            case WidgetType.CUSTOM_EDITTEXT:
+                return CUSTOM_EDITTEXT;
+            case WidgetType.CUSTOM_UNSELECTABLE_TEXTVIEW:
+                return CUSTOM_UNSELECTABLE_TEXTVIEW;
             default:
-                widgetType = UNKNOWN;
+                return UNKNOWN;
         }
-        final String version = event == null
+    }
+
+    private String getModelName(@Nullable SelectionEvent event) {
+        return event == null
                 ? SelectionEvent.NO_VERSION_TAG
                 : Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
-        return String.format("%s/%s", widgetType, version);
     }
 
     private static String createSessionId() {
         return UUID.randomUUID().toString();
     }
 
-    private static int clamp(int val) {
-        return Math.max(Math.min(val, Short.MAX_VALUE), Short.MIN_VALUE);
-    }
-
     private static void debugLog(LogMaker log) {
         if (!DEBUG_LOG_ENABLED) return;
 
-        final String tag = Objects.toString(log.getTaggedData(VERSION_TAG), "tag");
+        final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
+        final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
+        final String widget = widgetVersion.isEmpty()
+                ? widgetType : widgetType + "-" + widgetVersion;
         final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
         if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
             String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
             sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
-            Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
+            Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
         }
 
+        final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
+        final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
         final String type = getLogTypeString(log.getType());
-        final String subType = getLogSubTypeString(log.getSubtype());
+        final int smartStart = Integer.parseInt(
+                Objects.toString(log.getTaggedData(SMART_START), ZERO));
+        final int smartEnd = Integer.parseInt(
+                Objects.toString(log.getTaggedData(SMART_END), ZERO));
+        final int eventStart = Integer.parseInt(
+                Objects.toString(log.getTaggedData(EVENT_START), ZERO));
+        final int eventEnd = Integer.parseInt(
+                Objects.toString(log.getTaggedData(EVENT_END), ZERO));
 
-        final int smartIndices = Integer.parseInt(
-                Objects.toString(log.getTaggedData(SMART_INDICES), ZERO));
-        final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16);
-        final int smartEnd = (short) (smartIndices & 0xffff);
-
-        final int eventIndices = Integer.parseInt(
-                Objects.toString(log.getTaggedData(EVENT_INDICES), ZERO));
-        final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16);
-        final int eventEnd = (short) (eventIndices & 0xffff);
-
-        Log.d(LOG_TAG, String.format("%2d: %s/%s, context=%d,%d - old=%d,%d (%s)",
-                index, type, subType, eventStart, eventEnd, smartStart, smartEnd, tag));
+        Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
+                index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model));
     }
 
     /**
@@ -369,12 +358,12 @@
         /**
          * Use this to specify an indeterminate positive index.
          */
-        public static final int OUT_OF_BOUNDS = Short.MAX_VALUE;
+        public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE;
 
         /**
          * Use this to specify an indeterminate negative index.
          */
-        public static final int OUT_OF_BOUNDS_NEGATIVE = Short.MIN_VALUE;
+        public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE;
 
         private static final String NO_VERSION_TAG = "";
 
diff --git a/android/webkit/ServiceWorkerClient.java b/android/webkit/ServiceWorkerClient.java
index d6e8f36..9124c85 100644
--- a/android/webkit/ServiceWorkerClient.java
+++ b/android/webkit/ServiceWorkerClient.java
@@ -29,9 +29,9 @@
      * application to return the data. If the return value is {@code null}, the
      * Service Worker will continue to load the resource as usual.
      * Otherwise, the return response and data will be used.
-     * NOTE: This method is called on a thread other than the UI thread
-     * so clients should exercise caution when accessing private data
-     * or the view system.
+     *
+     * <p class="note"><b>Note:</b> This method is called on a thread other than the UI thread so
+     * clients should exercise caution when accessing private data or the view system.
      *
      * @param request Object containing the details of the request.
      * @return A {@link android.webkit.WebResourceResponse} containing the
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 259bf60..665d694 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -191,7 +191,7 @@
  * {@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
+ * <p class="note"><b>Note:</b> 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.
  *
@@ -308,10 +308,15 @@
  * 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:
+ * tag in its manifest's {@code <application>} element:
  * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- *            android:value="true" /&gt;
+ * &lt;manifest&gt;
+ *     &lt;application&gt;
+ *         ...
+ *         &lt;meta-data android:name=&quot;android.webkit.WebView.MetricsOptOut&quot;
+ *             android:value=&quot;true&quot; /&gt;
+ *     &lt;/application&gt;
+ * &lt;/manifest&gt;
  * </pre>
  * <p>
  * Data will only be uploaded for a given app if the user has consented AND the app has not opted
@@ -323,11 +328,17 @@
  * 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:
+ * The recommended way for apps to enable the feature is putting the following tag in the manifest's
+ * {@code <application>} element:
  * <p>
  * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- *            android:value="true" /&gt;
+ * &lt;manifest&gt;
+ *     &lt;application&gt;
+ *         ...
+ *         &lt;meta-data android:name=&quot;android.webkit.WebView.EnableSafeBrowsing&quot;
+ *             android:value=&quot;true&quot; /&gt;
+ *     &lt;/application&gt;
+ * &lt;/manifest&gt;
  * </pre>
  *
  */
@@ -536,9 +547,13 @@
     }
 
     /**
-     * Constructs a new WebView with a Context object.
+     * Constructs a new WebView with an Activity Context object.
      *
-     * @param context a Context object used to access application assets
+     * <p class="note"><b>Note:</b> WebView should always be instantiated with an Activity Context.
+     * If instantiated with an Application Context, WebView will be unable to provide several
+     * features, such as JavaScript dialogs and autofill.
+     *
+     * @param context an Activity Context to access application assets
      */
     public WebView(Context context) {
         this(context, null);
@@ -547,7 +562,7 @@
     /**
      * Constructs a new WebView with layout parameters.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context to access application assets
      * @param attrs an AttributeSet passed to our parent
      */
     public WebView(Context context, AttributeSet attrs) {
@@ -557,7 +572,7 @@
     /**
      * Constructs a new WebView with layout parameters and a default style.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context 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
@@ -570,7 +585,7 @@
     /**
      * Constructs a new WebView with layout parameters and a default style.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context 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
@@ -587,7 +602,7 @@
     /**
      * Constructs a new WebView with layout parameters and a default style.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context 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
@@ -612,7 +627,7 @@
      * 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 context an Activity Context 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
diff --git a/android/webkit/WebViewClient.java b/android/webkit/WebViewClient.java
index c5b64eb..517ad07 100644
--- a/android/webkit/WebViewClient.java
+++ b/android/webkit/WebViewClient.java
@@ -130,7 +130,7 @@
      * <p>This method is called when the body of the HTTP response has started loading, is reflected
      * in the DOM, and will be visible in subsequent draws. This callback occurs early in the
      * document loading process, and as such you should expect that linked resources (for example,
-     * css and images) may not be available.
+     * CSS and images) may not be available.
      *
      * <p>For more fine-grained notification of visual state updates, see {@link
      * WebView#postVisualStateCallback}.
@@ -150,13 +150,15 @@
      * Notify the host application of a resource request and allow the
      * application to return the data.  If the return value is {@code null}, the WebView
      * will continue to load the resource as usual.  Otherwise, the return
-     * response and data will be used.  NOTE: This method is called on a thread
+     * response and data will be used.
+     *
+     * <p class="note"><b>Note:</b> This method is called on a thread
      * other than the UI thread so clients should exercise caution
      * when accessing private data or the view system.
      *
-     * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
-     * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
-     * the warning with {@link #onSafeBrowsingHit}.
+     * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+     * Browsing checks. If this is undesired, whitelist the URL with {@link
+     * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
      *
      * @param view The {@link android.webkit.WebView} that is requesting the
      *             resource.
@@ -178,13 +180,15 @@
      * Notify the host application of a resource request and allow the
      * application to return the data.  If the return value is {@code null}, the WebView
      * will continue to load the resource as usual.  Otherwise, the return
-     * response and data will be used.  NOTE: This method is called on a thread
+     * response and data will be used.
+     *
+     * <p class="note"><b>Note:</b> This method is called on a thread
      * other than the UI thread so clients should exercise caution
      * when accessing private data or the view system.
      *
-     * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
-     * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
-     * the warning with {@link #onSafeBrowsingHit}.
+     * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+     * Browsing checks. If this is undesired, whitelist the URL with {@link
+     * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
      *
      * @param view The {@link android.webkit.WebView} that is requesting the
      *             resource.
@@ -248,7 +252,7 @@
     public static final int ERROR_FILE_NOT_FOUND = -14;
     /** Too many requests during this load */
     public static final int ERROR_TOO_MANY_REQUESTS = -15;
-    /** Resource load was cancelled by Safe Browsing */
+    /** Resource load was canceled by Safe Browsing */
     public static final int ERROR_UNSAFE_RESOURCE = -16;
 
     /** @hide */
@@ -272,8 +276,8 @@
 
     /**
      * Report an error to the host application. These errors are unrecoverable
-     * (i.e. the main resource is unavailable). The errorCode parameter
-     * corresponds to one of the ERROR_* constants.
+     * (i.e. the main resource is unavailable). The {@code errorCode} parameter
+     * corresponds to one of the {@code ERROR_*} constants.
      * @param view The WebView that is initiating the callback.
      * @param errorCode The error code corresponding to an ERROR_* value.
      * @param description A String describing the error.
@@ -289,11 +293,11 @@
     /**
      * Report web resource loading error to the host application. These errors usually indicate
      * inability to connect to the server. Note that unlike the deprecated version of the callback,
-     * the new version will be called for any resource (iframe, image, etc), not just for the main
+     * the new version will be called for any resource (iframe, image, etc.), not just for the main
      * page. Thus, it is recommended to perform minimum required work in this callback.
      * @param view The WebView that is initiating the callback.
      * @param request The originating request.
-     * @param error Information about the error occured.
+     * @param error Information about the error occurred.
      */
     public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
         if (request.isForMainFrame()) {
@@ -306,12 +310,12 @@
     /**
      * Notify the host application that an HTTP error has been received from the server while
      * loading a resource.  HTTP errors have status codes &gt;= 400.  This callback will be called
-     * for any resource (iframe, image, etc), not just for the main page. Thus, it is recommended to
-     * perform minimum required work in this callback. Note that the content of the server
-     * response may not be provided within the <b>errorResponse</b> parameter.
+     * for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended
+     * to perform minimum required work in this callback. Note that the content of the server
+     * response may not be provided within the {@code errorResponse} parameter.
      * @param view The WebView that is initiating the callback.
      * @param request The originating request.
-     * @param errorResponse Information about the error occured.
+     * @param errorResponse Information about the error occurred.
      */
     public void onReceivedHttpError(
             WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
@@ -365,7 +369,7 @@
      * if desired and providing the keys. There are three ways to
      * respond: proceed(), cancel() or ignore(). Webview stores the response
      * in memory (for the life of the application) if proceed() or cancel() is
-     * called and does not call onReceivedClientCertRequest() again for the
+     * called and does not call {@code onReceivedClientCertRequest()} again for the
      * same host and port pair. Webview does not store the response if ignore()
      * is called. Note that, multiple layers in chromium network stack might be
      * caching the responses, so the behavior for ignore is only a best case
@@ -432,7 +436,7 @@
     /**
      * Notify the host application that a key was not handled by the WebView.
      * Except system keys, WebView always consumes the keys in the normal flow
-     * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+     * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
      * from where the key is dispatched. It gives the host application a chance
      * to handle the unhandled key events.
      *
@@ -446,7 +450,7 @@
     /**
      * Notify the host application that a input event was not handled by the WebView.
      * Except system keys, WebView always consumes input events in the normal flow
-     * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+     * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
      * from where the event is dispatched. It gives the host application a chance
      * to handle the unhandled input events.
      *
@@ -503,7 +507,7 @@
     }
 
     /**
-     * Notify host application that the given webview's render process has exited.
+     * Notify host application that the given WebView's render process has exited.
      *
      * Multiple WebView instances may be associated with a single render process;
      * onRenderProcessGone will be called for each WebView that was affected.
@@ -513,10 +517,10 @@
      *
      * The given WebView can't be used, and should be removed from the view hierarchy,
      * all references to it should be cleaned up, e.g any references in the Activity
-     * or other classes saved using findViewById and similar calls, etc
+     * or other classes saved using {@link android.view.View#findViewById} and similar calls, etc.
      *
      * To cause an render process crash for test purpose, the application can
-     * call loadUrl("chrome://crash") on the WebView. Note that multiple WebView
+     * call {@code loadUrl("chrome://crash")} on the WebView. Note that multiple WebView
      * instances may be affected if they share a render process, not just the
      * specific WebView which loaded chrome://crash.
      *
@@ -537,12 +541,13 @@
      * behavior is to show an interstitial to the user, with the reporting checkbox visible.
      *
      * If the application needs to show its own custom interstitial UI, the callback can be invoked
-     * asynchronously with backToSafety() or proceed(), depending on user response.
+     * asynchronously with {@link SafeBrowsingResponse#backToSafety} or {@link
+     * SafeBrowsingResponse#proceed}, depending on user response.
      *
      * @param view The WebView that hit the malicious resource.
      * @param request Object containing the details of the request.
      * @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
-     *                   SAFE_BROWSING_THREAT_* value.
+     *                   {@code SAFE_BROWSING_THREAT_*} value.
      * @param callback Applications must invoke one of the callback methods.
      */
     public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
diff --git a/android/webkit/WebViewFragment.java b/android/webkit/WebViewFragment.java
index d803f62..e5b7c8d 100644
--- a/android/webkit/WebViewFragment.java
+++ b/android/webkit/WebViewFragment.java
@@ -27,7 +27,10 @@
  * A fragment that displays a WebView.
  * <p>
  * The WebView is automically paused or resumed when the Fragment is paused or resumed.
+ *
+ * @deprecated Manually call {@link WebView#onPause()} and {@link WebView#onResume()}
  */
+@Deprecated
 public class WebViewFragment extends Fragment {
     private WebView mWebView;
     private boolean mIsWebViewAvailable;
diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java
index 341c69f..de0b97d 100644
--- a/android/webkit/WebViewLibraryLoader.java
+++ b/android/webkit/WebViewLibraryLoader.java
@@ -229,7 +229,9 @@
 
     /**
      * Load WebView's native library into the current process.
-     * Note: assumes that we have waited for relro creation.
+     *
+     * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
+     *
      * @param clazzLoader class loader used to find the linker namespace to load the library into.
      * @param packageInfo the package from which WebView is loaded.
      */
diff --git a/android/webkit/WebViewProvider.java b/android/webkit/WebViewProvider.java
index c46c681..a896925 100644
--- a/android/webkit/WebViewProvider.java
+++ b/android/webkit/WebViewProvider.java
@@ -316,7 +316,7 @@
     /**
      * Provides mechanism for the name-sake methods declared in View and ViewGroup to be delegated
      * into the WebViewProvider instance.
-     * NOTE For many of these methods, the WebView will provide a super.Foo() call before or after
+     * NOTE: For many of these methods, the WebView will provide a super.Foo() call before or after
      * making the call into the provider instance. This is done for convenience in the common case
      * of maintaining backward compatibility. For remaining super class calls (e.g. where the
      * provider may need to only conditionally make the call based on some internal state) see the
diff --git a/android/widget/AbsListView.java b/android/widget/AbsListView.java
index 170582b..e0c897d 100644
--- a/android/widget/AbsListView.java
+++ b/android/widget/AbsListView.java
@@ -3866,6 +3866,7 @@
     private void onTouchDown(MotionEvent ev) {
         mHasPerformedLongPress = false;
         mActivePointerId = ev.getPointerId(0);
+        hideSelector();
 
         if (mTouchMode == TOUCH_MODE_OVERFLING) {
             // Stopped the fling. It is a scroll.
@@ -5226,17 +5227,21 @@
         }
 
         mRecycler.fullyDetachScrapViews();
+        boolean selectorOnScreen = false;
         if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
             final int childIndex = mSelectedPosition - mFirstPosition;
             if (childIndex >= 0 && childIndex < getChildCount()) {
                 positionSelector(mSelectedPosition, getChildAt(childIndex));
+                selectorOnScreen = true;
             }
         } else if (mSelectorPosition != INVALID_POSITION) {
             final int childIndex = mSelectorPosition - mFirstPosition;
             if (childIndex >= 0 && childIndex < getChildCount()) {
-                positionSelector(INVALID_POSITION, getChildAt(childIndex));
+                positionSelector(mSelectorPosition, getChildAt(childIndex));
+                selectorOnScreen = true;
             }
-        } else {
+        }
+        if (!selectorOnScreen) {
             mSelectorRect.setEmpty();
         }
 
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index e6da69d..d477ffd 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -3842,14 +3842,10 @@
                 mProcessTextIntentActionsHandler.onInitializeMenu(menu);
             }
 
-            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
-                if (mHasSelection && !mTextView.hasTransientState()) {
-                    mTextView.setHasTransientState(true);
-                }
-                return true;
-            } else {
-                return false;
+            if (mHasSelection && !mTextView.hasTransientState()) {
+                mTextView.setHasTransientState(true);
             }
+            return true;
         }
 
         private Callback getCustomCallback() {
@@ -6557,12 +6553,12 @@
          * Adds "PROCESS_TEXT" menu items to the specified menu.
          */
         public void onInitializeMenu(Menu menu) {
-            final int size = mSupportedActivities.size();
             loadSupportedActivities();
+            final int size = mSupportedActivities.size();
             for (int i = 0; i < size; i++) {
                 final ResolveInfo resolveInfo = mSupportedActivities.get(i);
                 menu.add(Menu.NONE, Menu.NONE,
-                        Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i++,
+                        Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
                         getLabel(resolveInfo))
                         .setIntent(createProcessTextIntentForResolveInfo(resolveInfo))
                         .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index 8dc8cab..e91db13 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -1354,6 +1354,7 @@
         }
 
         mDecorView = createDecorView(mBackgroundView);
+        mDecorView.setIsRootNamespace(true);
 
         // The background owner should be elevated so that it casts a shadow.
         mBackgroundView.setElevation(mElevation);
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 5e22650..d0ad27a 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -20,10 +20,12 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
+import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.LocaleList;
 import android.text.Layout;
 import android.text.Selection;
@@ -81,6 +83,7 @@
         mEditor = Preconditions.checkNotNull(editor);
         mTextView = mEditor.getTextView();
         mTextClassificationHelper = new TextClassificationHelper(
+                mTextView.getContext(),
                 mTextView.getTextClassifier(),
                 getText(mTextView),
                 0, 1, mTextView.getTextLocales());
@@ -385,6 +388,7 @@
 
     private void resetTextClassificationHelper() {
         mTextClassificationHelper.init(
+                mTextView.getContext(),
                 mTextView.getTextClassifier(),
                 getText(mTextView),
                 mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -587,7 +591,9 @@
             Preconditions.checkNotNull(textView);
             final @SmartSelectionEventTracker.WidgetType int widgetType = textView.isTextEditable()
                     ? SmartSelectionEventTracker.WidgetType.EDITTEXT
-                    : SmartSelectionEventTracker.WidgetType.TEXTVIEW;
+                    : (textView.isTextSelectable()
+                            ? SmartSelectionEventTracker.WidgetType.TEXTVIEW
+                            : SmartSelectionEventTracker.WidgetType.UNSELECTABLE_TEXTVIEW);
             mDelegate = new SmartSelectionEventTracker(textView.getContext(), widgetType);
             mEditTextLogger = textView.isTextEditable();
             mWordIterator = BreakIterator.getWordInstance(textView.getTextLocale());
@@ -787,6 +793,7 @@
 
         private static final int TRIM_DELTA = 120;  // characters
 
+        private Context mContext;
         private TextClassifier mTextClassifier;
 
         /** The original TextView text. **/
@@ -795,7 +802,10 @@
         private int mSelectionStart;
         /** End index relative to mText. */
         private int mSelectionEnd;
-        private LocaleList mLocales;
+
+        private final TextSelection.Options mSelectionOptions = new TextSelection.Options();
+        private final TextClassification.Options mClassificationOptions =
+                new TextClassification.Options();
 
         /** Trimmed text starting from mTrimStart in mText. */
         private CharSequence mTrimmedText;
@@ -816,21 +826,24 @@
         /** Whether the TextClassifier has been initialized. */
         private boolean mHot;
 
-        TextClassificationHelper(TextClassifier textClassifier,
+        TextClassificationHelper(Context context, TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
-            init(textClassifier, text, selectionStart, selectionEnd, locales);
+            init(context, textClassifier, text, selectionStart, selectionEnd, locales);
         }
 
         @UiThread
-        public void init(TextClassifier textClassifier,
+        public void init(Context context, TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
+            mContext = Preconditions.checkNotNull(context);
             mTextClassifier = Preconditions.checkNotNull(textClassifier);
             mText = Preconditions.checkNotNull(text).toString();
             mLastClassificationText = null; // invalidate.
             Preconditions.checkArgument(selectionEnd > selectionStart);
             mSelectionStart = selectionStart;
             mSelectionEnd = selectionEnd;
-            mLocales = locales;
+            mClassificationOptions.setDefaultLocales(locales);
+            mSelectionOptions.setDefaultLocales(locales)
+                    .setDarkLaunchAllowed(true);
         }
 
         @WorkerThread
@@ -843,8 +856,16 @@
         public SelectionResult suggestSelection() {
             mHot = true;
             trimText();
-            final TextSelection selection = mTextClassifier.suggestSelection(
-                    mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
+            final TextSelection selection;
+            if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+                selection = mTextClassifier.suggestSelection(
+                        mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
+            } else {
+                // Use old APIs.
+                selection = mTextClassifier.suggestSelection(
+                        mTrimmedText, mRelativeStart, mRelativeEnd,
+                        mSelectionOptions.getDefaultLocales());
+            }
             // Do not classify new selection boundaries if TextClassifier should be dark launched.
             if (!mTextClassifier.getSettings().isDarkLaunch()) {
                 mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
@@ -874,20 +895,28 @@
             if (!Objects.equals(mText, mLastClassificationText)
                     || mSelectionStart != mLastClassificationSelectionStart
                     || mSelectionEnd != mLastClassificationSelectionEnd
-                    || !Objects.equals(mLocales, mLastClassificationLocales)) {
+                    || !Objects.equals(
+                            mClassificationOptions.getDefaultLocales(),
+                            mLastClassificationLocales)) {
 
                 mLastClassificationText = mText;
                 mLastClassificationSelectionStart = mSelectionStart;
                 mLastClassificationSelectionEnd = mSelectionEnd;
-                mLastClassificationLocales = mLocales;
+                mLastClassificationLocales = mClassificationOptions.getDefaultLocales();
 
                 trimText();
+                final TextClassification classification;
+                if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+                    classification = mTextClassifier.classifyText(
+                            mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
+                } else {
+                    // Use old APIs.
+                    classification = mTextClassifier.classifyText(
+                            mTrimmedText, mRelativeStart, mRelativeEnd,
+                            mClassificationOptions.getDefaultLocales());
+                }
                 mLastClassificationResult = new SelectionResult(
-                        mSelectionStart,
-                        mSelectionEnd,
-                        mTextClassifier.classifyText(
-                                mTrimmedText, mRelativeStart, mRelativeEnd, mLocales),
-                        selection);
+                        mSelectionStart, mSelectionEnd, classification, selection);
 
             }
             return mLastClassificationResult;